Use isocline instead of bestline for portability
Something more lightweight is definitely required.
This commit is contained in:
parent
297b58a4d5
commit
9d54898197
4
README
4
README
@ -136,8 +136,8 @@ Links & Acknowledgements
|
|||||||
[ 5] Universal TUN/TAP device driver by Maxim Krasnyansky and others
|
[ 5] Universal TUN/TAP device driver by Maxim Krasnyansky and others
|
||||||
https://www.kernel.org/doc/html/v5.12/networking/tuntap.html
|
https://www.kernel.org/doc/html/v5.12/networking/tuntap.html
|
||||||
|
|
||||||
[ 6] Bestline by Justine Tunney
|
[ 6] Isocline by Daan Leijen
|
||||||
https://github.com/jart/bestline
|
https://github.com/daanx/isocline
|
||||||
|
|
||||||
[ 7] em_inflate by Emmanuel Marty
|
[ 7] em_inflate by Emmanuel Marty
|
||||||
https://github.com/emmanuel-marty/em_inflate
|
https://github.com/emmanuel-marty/em_inflate
|
||||||
|
30
deps/bestline/LICENSE
vendored
30
deps/bestline/LICENSE
vendored
@ -1,30 +0,0 @@
|
|||||||
Bestline is released under the 2-clause BSD license.
|
|
||||||
|
|
||||||
Copyright (c) 2018-2021 Justine Tunney <jtunney@gmail.com>
|
|
||||||
Copyright (c) 2010-2016 Salvatore Sanfilippo <antirez@gmail.com>
|
|
||||||
Copyright (c) 2010-2013 Pieter Noordhuis <pcnoordhuis@gmail.com>
|
|
||||||
|
|
||||||
All rights reserved.
|
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without
|
|
||||||
modification, are permitted provided that the following conditions are
|
|
||||||
met:
|
|
||||||
|
|
||||||
* Redistributions of source code must retain the above copyright
|
|
||||||
notice, this list of conditions and the following disclaimer.
|
|
||||||
|
|
||||||
* Redistributions in binary form must reproduce the above copyright
|
|
||||||
notice, this list of conditions and the following disclaimer in the
|
|
||||||
documentation and/or other materials provided with the distribution.
|
|
||||||
|
|
||||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
||||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
||||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
|
||||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
|
||||||
HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|
||||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
|
||||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|
||||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
|
||||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
||||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
||||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
3583
deps/bestline/bestline.c
vendored
3583
deps/bestline/bestline.c
vendored
File diff suppressed because it is too large
Load Diff
42
deps/bestline/bestline.h
vendored
42
deps/bestline/bestline.h
vendored
@ -1,42 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <stdio.h>
|
|
||||||
|
|
||||||
typedef struct bestlineCompletions {
|
|
||||||
unsigned long len;
|
|
||||||
char **cvec;
|
|
||||||
} bestlineCompletions;
|
|
||||||
|
|
||||||
typedef void(bestlineCompletionCallback)(const char *, bestlineCompletions *);
|
|
||||||
typedef char *(bestlineHintsCallback)(const char *, const char **,
|
|
||||||
const char **);
|
|
||||||
typedef void(bestlineFreeHintsCallback)(void *);
|
|
||||||
typedef unsigned(bestlineXlatCallback)(unsigned);
|
|
||||||
|
|
||||||
void bestlineSetCompletionCallback(bestlineCompletionCallback *);
|
|
||||||
void bestlineSetHintsCallback(bestlineHintsCallback *);
|
|
||||||
void bestlineSetFreeHintsCallback(bestlineFreeHintsCallback *);
|
|
||||||
void bestlineAddCompletion(bestlineCompletions *, const char *);
|
|
||||||
void bestlineSetXlatCallback(bestlineXlatCallback *);
|
|
||||||
|
|
||||||
char *bestlineFile(const char *, FILE *, FILE *);
|
|
||||||
char *bestline(const char *);
|
|
||||||
char *bestlineRaw(const char *, int, int);
|
|
||||||
char *bestlineWithHistory(const char *, const char *);
|
|
||||||
int bestlineHistoryAdd(const char *);
|
|
||||||
int bestlineHistorySave(const char *);
|
|
||||||
int bestlineHistoryLoad(const char *);
|
|
||||||
void bestlineFreeCompletions(bestlineCompletions *);
|
|
||||||
void bestlineHistoryFree(void);
|
|
||||||
void bestlineClearScreen(int);
|
|
||||||
void bestlineMaskModeEnable(void);
|
|
||||||
void bestlineMaskModeDisable(void);
|
|
||||||
void bestlineDisableRawMode(void);
|
|
||||||
void bestlineFree(void *);
|
|
||||||
|
|
||||||
char bestlineIsSeparator(unsigned);
|
|
||||||
char bestlineNotSeparator(unsigned);
|
|
||||||
char bestlineIsXeparator(unsigned);
|
|
||||||
unsigned bestlineUppercase(unsigned);
|
|
||||||
unsigned bestlineLowercase(unsigned);
|
|
||||||
long bestlineReadCharacter(int, char *, unsigned long);
|
|
21
deps/isocline/LICENSE
vendored
Normal file
21
deps/isocline/LICENSE
vendored
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2021 Daan Leijen
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
627
deps/isocline/include/isocline.h
vendored
Normal file
627
deps/isocline/include/isocline.h
vendored
Normal file
@ -0,0 +1,627 @@
|
|||||||
|
/* ----------------------------------------------------------------------------
|
||||||
|
Copyright (c) 2021, Daan Leijen
|
||||||
|
This is free software; you can redistribute it and/or modify it
|
||||||
|
under the terms of the MIT License. A copy of the license can be
|
||||||
|
found in the "LICENSE" file at the root of this distribution.
|
||||||
|
-----------------------------------------------------------------------------*/
|
||||||
|
#pragma once
|
||||||
|
#ifndef IC_ISOCLINE_H
|
||||||
|
#define IC_ISOCLINE_H
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <stddef.h> // size_t
|
||||||
|
#include <stdbool.h> // bool
|
||||||
|
#include <stdint.h> // uint32_t
|
||||||
|
#include <stdarg.h> // term_vprintf
|
||||||
|
|
||||||
|
|
||||||
|
/*! \mainpage
|
||||||
|
Isocline C API reference.
|
||||||
|
|
||||||
|
Isocline is a pure C library that can be used as an alternative to the GNU readline library.
|
||||||
|
|
||||||
|
See the [Github repository](https://github.com/daanx/isocline#readme)
|
||||||
|
for general information and building the library.
|
||||||
|
|
||||||
|
Contents:
|
||||||
|
- \ref readline
|
||||||
|
- \ref bbcode
|
||||||
|
- \ref history
|
||||||
|
- \ref completion
|
||||||
|
- \ref highlight
|
||||||
|
- \ref options
|
||||||
|
- \ref helper
|
||||||
|
- \ref completex
|
||||||
|
- \ref term
|
||||||
|
- \ref async
|
||||||
|
- \ref alloc
|
||||||
|
*/
|
||||||
|
|
||||||
|
/// \defgroup readline Readline
|
||||||
|
/// The basic readline interface.
|
||||||
|
/// \{
|
||||||
|
|
||||||
|
/// Isocline version: 102 = 1.0.2.
|
||||||
|
#define IC_VERSION (104)
|
||||||
|
|
||||||
|
|
||||||
|
/// Read input from the user using rich editing abilities.
|
||||||
|
/// @param prompt_text The prompt text, can be NULL for the default ("").
|
||||||
|
/// The displayed prompt becomes `prompt_text` followed by the `prompt_marker` ("> ").
|
||||||
|
/// @returns the heap allocated input on succes, which should be `free`d by the caller.
|
||||||
|
/// Returns NULL on error, or if the user typed ctrl+d or ctrl+c.
|
||||||
|
///
|
||||||
|
/// If the standard input (`stdin`) has no editing capability
|
||||||
|
/// (like a dumb terminal (e.g. `TERM`=`dumb`), running in a debuggen, a pipe or redirected file, etc.)
|
||||||
|
/// the input is read directly from the input stream up to the
|
||||||
|
/// next line without editing capability.
|
||||||
|
/// See also \a ic_set_prompt_marker(), \a ic_style_def()
|
||||||
|
///
|
||||||
|
/// @see ic_set_prompt_marker(), ic_style_def()
|
||||||
|
char* ic_readline(const char* prompt_text);
|
||||||
|
|
||||||
|
/// \}
|
||||||
|
|
||||||
|
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/// \defgroup bbcode Formatted Text
|
||||||
|
/// Formatted text using [bbcode markup](https://github.com/daanx/isocline#bbcode-format).
|
||||||
|
/// \{
|
||||||
|
|
||||||
|
/// Print to the terminal while respection bbcode markup.
|
||||||
|
/// Any unclosed tags are closed automatically at the end of the print.
|
||||||
|
/// For example:
|
||||||
|
/// ```
|
||||||
|
/// ic_print("[b]bold, [i]bold and italic[/i], [red]red and bold[/][/b] default.");
|
||||||
|
/// ic_print("[b]bold[/], [i b]bold and italic[/], [yellow on blue]yellow on blue background");
|
||||||
|
/// ic_style_add("em","i color=#888800");
|
||||||
|
/// ic_print("[em]emphasis");
|
||||||
|
/// ```
|
||||||
|
/// Properties that can be assigned are:
|
||||||
|
/// * `color=` _clr_, `bgcolor=` _clr_: where _clr_ is either a hex value `#`RRGGBB or `#`RGB, a
|
||||||
|
/// standard HTML color name, or an ANSI palette name, like `ansi-maroon`, `ansi-default`, etc.
|
||||||
|
/// * `bold`,`italic`,`reverse`,`underline`: can be `on` or `off`.
|
||||||
|
/// * everything else is a style; all HTML and ANSI color names are also a style (so we can just use `red`
|
||||||
|
/// instead of `color=red`, or `on red` instead of `bgcolor=red`), and there are
|
||||||
|
/// the `b`, `i`, `u`, and `r` styles for bold, italic, underline, and reverse.
|
||||||
|
///
|
||||||
|
/// See [here](https://github.com/daanx/isocline#bbcode-format) for a description of the full bbcode format.
|
||||||
|
void ic_print( const char* s );
|
||||||
|
|
||||||
|
/// Print with bbcode markup ending with a newline.
|
||||||
|
/// @see ic_print()
|
||||||
|
void ic_println( const char* s );
|
||||||
|
|
||||||
|
/// Print formatted with bbcode markup.
|
||||||
|
/// @see ic_print()
|
||||||
|
void ic_printf(const char* fmt, ...);
|
||||||
|
|
||||||
|
/// Print formatted with bbcode markup.
|
||||||
|
/// @see ic_print
|
||||||
|
void ic_vprintf(const char* fmt, va_list args);
|
||||||
|
|
||||||
|
/// Define or redefine a style.
|
||||||
|
/// @param style_name The name of the style.
|
||||||
|
/// @param fmt The `fmt` string is the content of a tag and can contain
|
||||||
|
/// other styles. This is very useful to theme the output of a program
|
||||||
|
/// by assigning standard styles like `em` or `warning` etc.
|
||||||
|
void ic_style_def( const char* style_name, const char* fmt );
|
||||||
|
|
||||||
|
/// Start a global style that is only reset when calling a matching ic_style_close().
|
||||||
|
void ic_style_open( const char* fmt );
|
||||||
|
|
||||||
|
/// End a global style.
|
||||||
|
void ic_style_close(void);
|
||||||
|
|
||||||
|
/// \}
|
||||||
|
|
||||||
|
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
// History
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/// \defgroup history History
|
||||||
|
/// Readline input history.
|
||||||
|
/// \{
|
||||||
|
|
||||||
|
/// Enable history.
|
||||||
|
/// Use a \a NULL filename to not persist the history. Use -1 for max_entries to get the default (200).
|
||||||
|
void ic_set_history(const char* fname, long max_entries );
|
||||||
|
|
||||||
|
/// Remove the last entry in the history.
|
||||||
|
/// The last returned input from ic_readline() is automatically added to the history; this function removes it.
|
||||||
|
void ic_history_remove_last(void);
|
||||||
|
|
||||||
|
/// Clear the history.
|
||||||
|
void ic_history_clear(void);
|
||||||
|
|
||||||
|
/// Add an entry to the history
|
||||||
|
void ic_history_add( const char* entry );
|
||||||
|
|
||||||
|
/// \}
|
||||||
|
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
// Basic Completion
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
|
||||||
|
/// \defgroup completion Completion
|
||||||
|
/// Basic word completion.
|
||||||
|
/// \{
|
||||||
|
|
||||||
|
/// A completion environment
|
||||||
|
struct ic_completion_env_s;
|
||||||
|
|
||||||
|
/// A completion environment
|
||||||
|
typedef struct ic_completion_env_s ic_completion_env_t;
|
||||||
|
|
||||||
|
/// A completion callback that is called by isocline when tab is pressed.
|
||||||
|
/// It is passed a completion environment (containing the current input and the current cursor position),
|
||||||
|
/// the current input up-to the cursor (`prefix`)
|
||||||
|
/// and the user given argument when the callback was set.
|
||||||
|
/// When using completion transformers, like `ic_complete_quoted_word` the `prefix` contains the
|
||||||
|
/// the word to be completed without escape characters or quotes.
|
||||||
|
typedef void (ic_completer_fun_t)(ic_completion_env_t* cenv, const char* prefix );
|
||||||
|
|
||||||
|
/// Set the default completion handler.
|
||||||
|
/// @param completer The completion function
|
||||||
|
/// @param arg Argument passed to the \a completer.
|
||||||
|
/// There can only be one default completion function, setting it again disables the previous one.
|
||||||
|
/// The initial completer use `ic_complete_filename`.
|
||||||
|
void ic_set_default_completer( ic_completer_fun_t* completer, void* arg);
|
||||||
|
|
||||||
|
|
||||||
|
/// In a completion callback (usually from ic_complete_word()), use this function to add a completion.
|
||||||
|
/// (the completion string is copied by isocline and do not need to be preserved or allocated).
|
||||||
|
///
|
||||||
|
/// Returns `true` if the callback should continue trying to find more possible completions.
|
||||||
|
/// If `false` is returned, the callback should try to return and not add more completions (for improved latency).
|
||||||
|
bool ic_add_completion(ic_completion_env_t* cenv, const char* completion);
|
||||||
|
|
||||||
|
/// In a completion callback (usually from ic_complete_word()), use this function to add a completion.
|
||||||
|
/// The `display` is used to display the completion in the completion menu, and `help` is
|
||||||
|
/// displayed for hints for example. Both can be `NULL` for the default.
|
||||||
|
/// (all are copied by isocline and do not need to be preserved or allocated).
|
||||||
|
///
|
||||||
|
/// Returns `true` if the callback should continue trying to find more possible completions.
|
||||||
|
/// If `false` is returned, the callback should try to return and not add more completions (for improved latency).
|
||||||
|
bool ic_add_completion_ex( ic_completion_env_t* cenv, const char* completion, const char* display, const char* help );
|
||||||
|
|
||||||
|
/// In a completion callback (usually from ic_complete_word()), use this function to add completions.
|
||||||
|
/// The `completions` array should be terminated with a NULL element, and all elements
|
||||||
|
/// are added as completions if they start with `prefix`.
|
||||||
|
///
|
||||||
|
/// Returns `true` if the callback should continue trying to find more possible completions.
|
||||||
|
/// If `false` is returned, the callback should try to return and not add more completions (for improved latency).
|
||||||
|
bool ic_add_completions(ic_completion_env_t* cenv, const char* prefix, const char** completions);
|
||||||
|
|
||||||
|
/// Complete a filename.
|
||||||
|
/// Complete a filename given a semi-colon separated list of root directories `roots` and
|
||||||
|
/// semi-colon separated list of possible extensions (excluding directories).
|
||||||
|
/// If `roots` is NULL, the current directory is the root (".").
|
||||||
|
/// If `extensions` is NULL, any extension will match.
|
||||||
|
/// Each root directory should _not_ end with a directory separator.
|
||||||
|
/// If a directory is completed, the `dir_separator` is added at the end if it is not `0`.
|
||||||
|
/// Usually the `dir_separator` is `/` but it can be set to `\\` on Windows systems.
|
||||||
|
/// For example:
|
||||||
|
/// ```
|
||||||
|
/// /ho --> /home/
|
||||||
|
/// /home/.ba --> /home/.bashrc
|
||||||
|
/// ```
|
||||||
|
/// (This already uses ic_complete_quoted_word() so do not call it from inside a word handler).
|
||||||
|
void ic_complete_filename( ic_completion_env_t* cenv, const char* prefix, char dir_separator, const char* roots, const char* extensions );
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/// Function that returns whether a (utf8) character (of length `len`) is in a certain character class
|
||||||
|
/// @see ic_char_is_separator() etc.
|
||||||
|
typedef bool (ic_is_char_class_fun_t)(const char* s, long len);
|
||||||
|
|
||||||
|
|
||||||
|
/// Complete a _word_ (i.e. _token_).
|
||||||
|
/// Calls the user provided function `fun` to complete on the
|
||||||
|
/// current _word_. Almost all user provided completers should use this function.
|
||||||
|
/// If `is_word_char` is NULL, the default `&ic_char_is_nonseparator` is used.
|
||||||
|
/// The `prefix` passed to `fun` is modified to only contain the current word, and
|
||||||
|
/// any results from `ic_add_completion` are automatically adjusted to replace that part.
|
||||||
|
/// For example, on the input "hello w", a the user `fun` only gets `w` and can just complete
|
||||||
|
/// with "world" resulting in "hello world" without needing to consider `delete_before` etc.
|
||||||
|
/// @see ic_complete_qword() for completing quoted and escaped tokens.
|
||||||
|
void ic_complete_word(ic_completion_env_t* cenv, const char* prefix, ic_completer_fun_t* fun, ic_is_char_class_fun_t* is_word_char);
|
||||||
|
|
||||||
|
|
||||||
|
/// Complete a quoted _word_.
|
||||||
|
/// Calls the user provided function `fun` to complete while taking
|
||||||
|
/// care of quotes and escape characters. Almost all user provided completers should use
|
||||||
|
/// this function. The `prefix` passed to `fun` is modified to be unquoted and unescaped, and
|
||||||
|
/// any results from `ic_add_completion` are automatically quoted and escaped again.
|
||||||
|
/// For example, completing `hello world`, the `fun` always just completes `hel` or `hello w` to `hello world`,
|
||||||
|
/// but depending on user input, it will complete as:
|
||||||
|
/// ```
|
||||||
|
/// hel --> hello\ world
|
||||||
|
/// hello\ w --> hello\ world
|
||||||
|
/// hello w --> # no completion, the word is just 'w'>
|
||||||
|
/// "hel --> "hello world"
|
||||||
|
/// "hello w --> "hello world"
|
||||||
|
/// ```
|
||||||
|
/// with proper quotes and escapes.
|
||||||
|
/// If `is_word_char` is NULL, the default `&ic_char_is_nonseparator` is used.
|
||||||
|
/// @see ic_complete_quoted_word() to customize the word boundary, quotes etc.
|
||||||
|
void ic_complete_qword( ic_completion_env_t* cenv, const char* prefix, ic_completer_fun_t* fun, ic_is_char_class_fun_t* is_word_char );
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/// Complete a _word_.
|
||||||
|
/// Calls the user provided function `fun` to complete while taking
|
||||||
|
/// care of quotes and escape characters. Almost all user provided completers should use this function.
|
||||||
|
/// The `is_word_char` is a set of characters that are part of a "word". Use NULL for the default (`&ic_char_is_nonseparator`).
|
||||||
|
/// The `escape_char` is the escaping character, usually `\` but use 0 to not have escape characters.
|
||||||
|
/// The `quote_chars` define the quotes, use NULL for the default `"\'\""` quotes.
|
||||||
|
/// @see ic_complete_word() which uses the default values for `non_word_chars`, `quote_chars` and `\` for escape characters.
|
||||||
|
void ic_complete_qword_ex( ic_completion_env_t* cenv, const char* prefix, ic_completer_fun_t fun,
|
||||||
|
ic_is_char_class_fun_t* is_word_char, char escape_char, const char* quote_chars );
|
||||||
|
|
||||||
|
/// \}
|
||||||
|
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/// \defgroup highlight Syntax Highlighting
|
||||||
|
/// Basic syntax highlighting.
|
||||||
|
/// \{
|
||||||
|
|
||||||
|
/// A syntax highlight environment
|
||||||
|
struct ic_highlight_env_s;
|
||||||
|
typedef struct ic_highlight_env_s ic_highlight_env_t;
|
||||||
|
|
||||||
|
/// A syntax highlighter callback that is called by readline to syntax highlight user input.
|
||||||
|
typedef void (ic_highlight_fun_t)(ic_highlight_env_t* henv, const char* input, void* arg);
|
||||||
|
|
||||||
|
/// Set a syntax highlighter.
|
||||||
|
/// There can only be one highlight function, setting it again disables the previous one.
|
||||||
|
void ic_set_default_highlighter(ic_highlight_fun_t* highlighter, void* arg);
|
||||||
|
|
||||||
|
/// Set the style of characters starting at position `pos`.
|
||||||
|
void ic_highlight(ic_highlight_env_t* henv, long pos, long count, const char* style );
|
||||||
|
|
||||||
|
/// Experimental: Convenience callback for a function that highlights `s` using bbcode's.
|
||||||
|
/// The returned string should be allocated and is free'd by the caller.
|
||||||
|
typedef char* (ic_highlight_format_fun_t)(const char* s, void* arg);
|
||||||
|
|
||||||
|
/// Experimental: Convenience function for highlighting with bbcodes.
|
||||||
|
/// Can be called in a `ic_highlight_fun_t` callback to colorize the `input` using the
|
||||||
|
/// the provided `formatted` input that is the styled `input` with bbcodes. The
|
||||||
|
/// content of `formatted` without bbcode tags should match `input` exactly.
|
||||||
|
void ic_highlight_formatted(ic_highlight_env_t* henv, const char* input, const char* formatted);
|
||||||
|
|
||||||
|
/// \}
|
||||||
|
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
// Readline with a specific completer and highlighter
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
|
||||||
|
/// \defgroup readline
|
||||||
|
/// \{
|
||||||
|
|
||||||
|
/// Read input from the user using rich editing abilities,
|
||||||
|
/// using a particular completion function and highlighter for this call only.
|
||||||
|
/// both can be NULL in which case the defaults are used.
|
||||||
|
/// @see ic_readline(), ic_set_prompt_marker(), ic_set_default_completer(), ic_set_default_highlighter().
|
||||||
|
char* ic_readline_ex(const char* prompt_text, ic_completer_fun_t* completer, void* completer_arg,
|
||||||
|
ic_highlight_fun_t* highlighter, void* highlighter_arg);
|
||||||
|
|
||||||
|
/// \}
|
||||||
|
|
||||||
|
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
// Options
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
|
||||||
|
/// \defgroup options Options
|
||||||
|
/// \{
|
||||||
|
|
||||||
|
/// Set a prompt marker and a potential marker for extra lines with multiline input.
|
||||||
|
/// Pass \a NULL for the `prompt_marker` for the default marker (`"> "`).
|
||||||
|
/// Pass \a NULL for continuation prompt marker to make it equal to the `prompt_marker`.
|
||||||
|
void ic_set_prompt_marker( const char* prompt_marker, const char* continuation_prompt_marker );
|
||||||
|
|
||||||
|
/// Get the current prompt marker.
|
||||||
|
const char* ic_get_prompt_marker(void);
|
||||||
|
|
||||||
|
/// Get the current continuation prompt marker.
|
||||||
|
const char* ic_get_continuation_prompt_marker(void);
|
||||||
|
|
||||||
|
/// Disable or enable multi-line input (enabled by default).
|
||||||
|
/// Returns the previous setting.
|
||||||
|
bool ic_enable_multiline( bool enable );
|
||||||
|
|
||||||
|
/// Disable or enable sound (enabled by default).
|
||||||
|
/// A beep is used when tab cannot find any completion for example.
|
||||||
|
/// Returns the previous setting.
|
||||||
|
bool ic_enable_beep( bool enable );
|
||||||
|
|
||||||
|
/// Disable or enable color output (enabled by default).
|
||||||
|
/// Returns the previous setting.
|
||||||
|
bool ic_enable_color( bool enable );
|
||||||
|
|
||||||
|
/// Disable or enable duplicate entries in the history (disabled by default).
|
||||||
|
/// Returns the previous setting.
|
||||||
|
bool ic_enable_history_duplicates( bool enable );
|
||||||
|
|
||||||
|
/// Disable or enable automatic tab completion after a completion
|
||||||
|
/// to expand as far as possible if the completions are unique. (disabled by default).
|
||||||
|
/// Returns the previous setting.
|
||||||
|
bool ic_enable_auto_tab( bool enable );
|
||||||
|
|
||||||
|
/// Disable or enable preview of a completion selection (enabled by default)
|
||||||
|
/// Returns the previous setting.
|
||||||
|
bool ic_enable_completion_preview( bool enable );
|
||||||
|
|
||||||
|
/// Disable or enable automatic identation of continuation lines in multiline
|
||||||
|
/// input so it aligns with the initial prompt.
|
||||||
|
/// Returns the previous setting.
|
||||||
|
bool ic_enable_multiline_indent(bool enable);
|
||||||
|
|
||||||
|
/// Disable or enable display of short help messages for history search etc.
|
||||||
|
/// (full help is always dispayed when pressing F1 regardless of this setting)
|
||||||
|
/// @returns the previous setting.
|
||||||
|
bool ic_enable_inline_help(bool enable);
|
||||||
|
|
||||||
|
/// Disable or enable hinting (enabled by default)
|
||||||
|
/// Shows a hint inline when there is a single possible completion.
|
||||||
|
/// @returns the previous setting.
|
||||||
|
bool ic_enable_hint(bool enable);
|
||||||
|
|
||||||
|
/// Set millisecond delay before a hint is displayed. Can be zero. (500ms by default).
|
||||||
|
long ic_set_hint_delay(long delay_ms);
|
||||||
|
|
||||||
|
/// Disable or enable syntax highlighting (enabled by default).
|
||||||
|
/// This applies regardless whether a syntax highlighter callback was set (`ic_set_highlighter`)
|
||||||
|
/// Returns the previous setting.
|
||||||
|
bool ic_enable_highlight(bool enable);
|
||||||
|
|
||||||
|
|
||||||
|
/// Set millisecond delay for reading escape sequences in order to distinguish
|
||||||
|
/// a lone ESC from the start of a escape sequence. The defaults are 100ms and 10ms,
|
||||||
|
/// but it may be increased if working with very slow terminals.
|
||||||
|
void ic_set_tty_esc_delay(long initial_delay_ms, long followup_delay_ms);
|
||||||
|
|
||||||
|
/// Enable highlighting of matching braces (and error highlight unmatched braces).`
|
||||||
|
bool ic_enable_brace_matching(bool enable);
|
||||||
|
|
||||||
|
/// Set matching brace pairs.
|
||||||
|
/// Pass \a NULL for the default `"()[]{}"`.
|
||||||
|
void ic_set_matching_braces(const char* brace_pairs);
|
||||||
|
|
||||||
|
/// Enable automatic brace insertion (enabled by default).
|
||||||
|
bool ic_enable_brace_insertion(bool enable);
|
||||||
|
|
||||||
|
/// Set matching brace pairs for automatic insertion.
|
||||||
|
/// Pass \a NULL for the default `()[]{}\"\"''`
|
||||||
|
void ic_set_insertion_braces(const char* brace_pairs);
|
||||||
|
|
||||||
|
/// \}
|
||||||
|
|
||||||
|
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
// Advanced Completion
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
|
||||||
|
/// \defgroup completex Advanced Completion
|
||||||
|
/// \{
|
||||||
|
|
||||||
|
/// Get the raw current input (and cursor position if `cursor` != NULL) for the completion.
|
||||||
|
/// Usually completer functions should look at their `prefix` though as transformers
|
||||||
|
/// like `ic_complete_word` may modify the prefix (for example, unescape it).
|
||||||
|
const char* ic_completion_input( ic_completion_env_t* cenv, long* cursor );
|
||||||
|
|
||||||
|
/// Get the completion argument passed to `ic_set_completer`.
|
||||||
|
void* ic_completion_arg( const ic_completion_env_t* cenv );
|
||||||
|
|
||||||
|
/// Do we have already some completions?
|
||||||
|
bool ic_has_completions( const ic_completion_env_t* cenv );
|
||||||
|
|
||||||
|
/// Do we already have enough completions and should we return if possible? (for improved latency)
|
||||||
|
bool ic_stop_completing( const ic_completion_env_t* cenv);
|
||||||
|
|
||||||
|
|
||||||
|
/// Primitive completion, cannot be used with most transformers (like `ic_complete_word` and `ic_complete_qword`).
|
||||||
|
/// When completed, `delete_before` _bytes_ are deleted before the cursor position,
|
||||||
|
/// `delete_after` _bytes_ are deleted after the cursor, and finally `completion` is inserted.
|
||||||
|
/// The `display` is used to display the completion in the completion menu, and `help` is displayed
|
||||||
|
/// with hinting. Both `display` and `help` can be NULL.
|
||||||
|
/// (all are copied by isocline and do not need to be preserved or allocated).
|
||||||
|
///
|
||||||
|
/// Returns `true` if the callback should continue trying to find more possible completions.
|
||||||
|
/// If `false` is returned, the callback should try to return and not add more completions (for improved latency).
|
||||||
|
bool ic_add_completion_prim( ic_completion_env_t* cenv, const char* completion,
|
||||||
|
const char* display, const char* help,
|
||||||
|
long delete_before, long delete_after);
|
||||||
|
|
||||||
|
/// \}
|
||||||
|
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/// \defgroup helper Character Classes.
|
||||||
|
/// Convenience functions for character classes, highlighting and completion.
|
||||||
|
/// \{
|
||||||
|
|
||||||
|
/// Convenience: return the position of a previous code point in a UTF-8 string `s` from postion `pos`.
|
||||||
|
/// Returns `-1` if `pos <= 0` or `pos > strlen(s)` (or other errors).
|
||||||
|
long ic_prev_char( const char* s, long pos );
|
||||||
|
|
||||||
|
/// Convenience: return the position of the next code point in a UTF-8 string `s` from postion `pos`.
|
||||||
|
/// Returns `-1` if `pos < 0` or `pos >= strlen(s)` (or other errors).
|
||||||
|
long ic_next_char( const char* s, long pos );
|
||||||
|
|
||||||
|
/// Convenience: does a string `s` starts with a given `prefix` ?
|
||||||
|
bool ic_starts_with( const char* s, const char* prefix );
|
||||||
|
|
||||||
|
/// Convenience: does a string `s` starts with a given `prefix` ignoring (ascii) case?
|
||||||
|
bool ic_istarts_with( const char* s, const char* prefix );
|
||||||
|
|
||||||
|
|
||||||
|
/// Convenience: character class for whitespace `[ \t\r\n]`.
|
||||||
|
bool ic_char_is_white(const char* s, long len);
|
||||||
|
|
||||||
|
/// Convenience: character class for non-whitespace `[^ \t\r\n]`.
|
||||||
|
bool ic_char_is_nonwhite(const char* s, long len);
|
||||||
|
|
||||||
|
/// Convenience: character class for separators.
|
||||||
|
/// (``[ \t\r\n,.;:/\\(){}\[\]]``.)
|
||||||
|
/// This is used for word boundaries in isocline.
|
||||||
|
bool ic_char_is_separator(const char* s, long len);
|
||||||
|
|
||||||
|
/// Convenience: character class for non-separators.
|
||||||
|
bool ic_char_is_nonseparator(const char* s, long len);
|
||||||
|
|
||||||
|
/// Convenience: character class for letters (`[A-Za-z]` and any unicode > 0x80).
|
||||||
|
bool ic_char_is_letter(const char* s, long len);
|
||||||
|
|
||||||
|
/// Convenience: character class for digits (`[0-9]`).
|
||||||
|
bool ic_char_is_digit(const char* s, long len);
|
||||||
|
|
||||||
|
/// Convenience: character class for hexadecimal digits (`[A-Fa-f0-9]`).
|
||||||
|
bool ic_char_is_hexdigit(const char* s, long len);
|
||||||
|
|
||||||
|
/// Convenience: character class for identifier letters (`[A-Za-z0-9_-]` and any unicode > 0x80).
|
||||||
|
bool ic_char_is_idletter(const char* s, long len);
|
||||||
|
|
||||||
|
/// Convenience: character class for filename letters (_not in_ " \t\r\n`@$><=;|&\{\}\(\)\[\]]").
|
||||||
|
bool ic_char_is_filename_letter(const char* s, long len);
|
||||||
|
|
||||||
|
|
||||||
|
/// Convenience: If this is a token start, return the length. Otherwise return 0.
|
||||||
|
long ic_is_token(const char* s, long pos, ic_is_char_class_fun_t* is_token_char);
|
||||||
|
|
||||||
|
/// Convenience: Does this match the specified token?
|
||||||
|
/// Ensures not to match prefixes or suffixes, and returns the length of the match (in bytes).
|
||||||
|
/// E.g. `ic_match_token("function",0,&ic_char_is_letter,"fun")` returns 0.
|
||||||
|
/// while `ic_match_token("fun x",0,&ic_char_is_letter,"fun"})` returns 3.
|
||||||
|
long ic_match_token(const char* s, long pos, ic_is_char_class_fun_t* is_token_char, const char* token);
|
||||||
|
|
||||||
|
|
||||||
|
/// Convenience: Do any of the specified tokens match?
|
||||||
|
/// Ensures not to match prefixes or suffixes, and returns the length of the match (in bytes).
|
||||||
|
/// E.g. `ic_match_any_token("function",0,&ic_char_is_letter,{"fun","func",NULL})` returns 0.
|
||||||
|
/// while `ic_match_any_token("func x",0,&ic_char_is_letter,{"fun","func",NULL})` returns 4.
|
||||||
|
long ic_match_any_token(const char* s, long pos, ic_is_char_class_fun_t* is_token_char, const char** tokens);
|
||||||
|
|
||||||
|
/// \}
|
||||||
|
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/// \defgroup term Terminal
|
||||||
|
///
|
||||||
|
/// Experimental: Low level terminal output.
|
||||||
|
/// Ensures basic ANSI SGR escape sequences are processed
|
||||||
|
/// in a portable way (e.g. on Windows)
|
||||||
|
/// \{
|
||||||
|
|
||||||
|
/// Initialize for terminal output.
|
||||||
|
/// Call this before using the terminal write functions (`ic_term_write`)
|
||||||
|
/// Does nothing on most platforms but on Windows it sets the console to UTF8 output and possible
|
||||||
|
/// enables virtual terminal processing.
|
||||||
|
void ic_term_init(void);
|
||||||
|
|
||||||
|
/// Call this when done with the terminal functions.
|
||||||
|
void ic_term_done(void);
|
||||||
|
|
||||||
|
/// Flush the terminal output.
|
||||||
|
/// (happens automatically on newline characters ('\n') as well).
|
||||||
|
void ic_term_flush(void);
|
||||||
|
|
||||||
|
/// Write a string to the console (and process CSI escape sequences).
|
||||||
|
void ic_term_write(const char* s);
|
||||||
|
|
||||||
|
/// Write a string to the console and end with a newline
|
||||||
|
/// (and process CSI escape sequences).
|
||||||
|
void ic_term_writeln(const char* s);
|
||||||
|
|
||||||
|
/// Write a formatted string to the console.
|
||||||
|
/// (and process CSI escape sequences)
|
||||||
|
void ic_term_writef(const char* fmt, ...);
|
||||||
|
|
||||||
|
/// Write a formatted string to the console.
|
||||||
|
void ic_term_vwritef(const char* fmt, va_list args);
|
||||||
|
|
||||||
|
/// Set text attributes from a style.
|
||||||
|
void ic_term_style( const char* style );
|
||||||
|
|
||||||
|
/// Set text attribute to bold.
|
||||||
|
void ic_term_bold(bool enable);
|
||||||
|
|
||||||
|
/// Set text attribute to underline.
|
||||||
|
void ic_term_underline(bool enable);
|
||||||
|
|
||||||
|
/// Set text attribute to italic.
|
||||||
|
void ic_term_italic(bool enable);
|
||||||
|
|
||||||
|
/// Set text attribute to reverse video.
|
||||||
|
void ic_term_reverse(bool enable);
|
||||||
|
|
||||||
|
/// Set text attribute to ansi color palette index between 0 and 255 (or 256 for the ANSI "default" color).
|
||||||
|
/// (auto matched to smaller palette if not supported)
|
||||||
|
void ic_term_color_ansi(bool foreground, int color);
|
||||||
|
|
||||||
|
/// Set text attribute to 24-bit RGB color (between `0x000000` and `0xFFFFFF`).
|
||||||
|
/// (auto matched to smaller palette if not supported)
|
||||||
|
void ic_term_color_rgb(bool foreground, uint32_t color );
|
||||||
|
|
||||||
|
/// Reset the text attributes.
|
||||||
|
void ic_term_reset( void );
|
||||||
|
|
||||||
|
/// Get the palette used by the terminal:
|
||||||
|
/// This is usually initialized from the COLORTERM environment variable. The
|
||||||
|
/// possible values of COLORTERM for each palette are given in parenthesis.
|
||||||
|
///
|
||||||
|
/// - 1: monochrome (`monochrome`)
|
||||||
|
/// - 3: old ANSI terminal with 8 colors, using bold for bright (`8color`/`3bit`)
|
||||||
|
/// - 4: regular ANSI terminal with 16 colors. (`16color`/`4bit`)
|
||||||
|
/// - 8: terminal with ANSI 256 color palette. (`256color`/`8bit`)
|
||||||
|
/// - 24: true-color terminal with full RGB colors. (`truecolor`/`24bit`/`direct`)
|
||||||
|
int ic_term_get_color_bits( void );
|
||||||
|
|
||||||
|
/// \}
|
||||||
|
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/// \defgroup async ASync
|
||||||
|
/// Async support
|
||||||
|
/// \{
|
||||||
|
|
||||||
|
/// Thread-safe way to asynchronously unblock a readline.
|
||||||
|
/// Behaves as if the user pressed the `ctrl-C` character
|
||||||
|
/// (resulting in returning NULL from `ic_readline`).
|
||||||
|
/// Returns `true` if the event was successfully delivered.
|
||||||
|
/// (This may not be supported on all platforms, but it is
|
||||||
|
/// functional on Linux, macOS and Windows).
|
||||||
|
bool ic_async_stop(void);
|
||||||
|
|
||||||
|
/// \}
|
||||||
|
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/// \defgroup alloc Custom Allocation
|
||||||
|
/// Register allocation functions for custom allocators
|
||||||
|
/// \{
|
||||||
|
|
||||||
|
typedef void* (ic_malloc_fun_t)( size_t size );
|
||||||
|
typedef void* (ic_realloc_fun_t)( void* p, size_t newsize );
|
||||||
|
typedef void (ic_free_fun_t)( void* p );
|
||||||
|
|
||||||
|
/// Initialize with custom allocation functions.
|
||||||
|
/// This must be called as the first function in a program!
|
||||||
|
void ic_init_custom_alloc( ic_malloc_fun_t* _malloc, ic_realloc_fun_t* _realloc, ic_free_fun_t* _free );
|
||||||
|
|
||||||
|
/// Free a potentially custom alloc'd pointer (in particular, the result returned from `ic_readline`)
|
||||||
|
void ic_free( void* p );
|
||||||
|
|
||||||
|
/// Allocate using the current memory allocator.
|
||||||
|
void* ic_malloc(size_t sz);
|
||||||
|
|
||||||
|
/// Duplicate a string using the current memory allocator.
|
||||||
|
const char* ic_strdup( const char* s );
|
||||||
|
|
||||||
|
/// \}
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif /// IC_ISOCLINE_H
|
294
deps/isocline/src/attr.c
vendored
Normal file
294
deps/isocline/src/attr.c
vendored
Normal file
@ -0,0 +1,294 @@
|
|||||||
|
/* ----------------------------------------------------------------------------
|
||||||
|
Copyright (c) 2021, Daan Leijen
|
||||||
|
This is free software; you can redistribute it and/or modify it
|
||||||
|
under the terms of the MIT License. A copy of the license can be
|
||||||
|
found in the "LICENSE" file at the root of this distribution.
|
||||||
|
-----------------------------------------------------------------------------*/
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include "common.h"
|
||||||
|
#include "stringbuf.h" // str_next_ofs
|
||||||
|
#include "attr.h"
|
||||||
|
#include "term.h" // color_from_ansi256
|
||||||
|
|
||||||
|
//-------------------------------------------------------------
|
||||||
|
// Attributes
|
||||||
|
//-------------------------------------------------------------
|
||||||
|
|
||||||
|
ic_private attr_t attr_none(void) {
|
||||||
|
attr_t attr;
|
||||||
|
attr.value = 0;
|
||||||
|
return attr;
|
||||||
|
}
|
||||||
|
|
||||||
|
ic_private attr_t attr_default(void) {
|
||||||
|
attr_t attr = attr_none();
|
||||||
|
attr.x.color = IC_ANSI_DEFAULT;
|
||||||
|
attr.x.bgcolor = IC_ANSI_DEFAULT;
|
||||||
|
attr.x.bold = IC_OFF;
|
||||||
|
attr.x.underline = IC_OFF;
|
||||||
|
attr.x.reverse = IC_OFF;
|
||||||
|
attr.x.italic = IC_OFF;
|
||||||
|
return attr;
|
||||||
|
}
|
||||||
|
|
||||||
|
ic_private bool attr_is_none(attr_t attr) {
|
||||||
|
return (attr.value == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
ic_private bool attr_is_eq(attr_t attr1, attr_t attr2) {
|
||||||
|
return (attr1.value == attr2.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
ic_private attr_t attr_from_color( ic_color_t color ) {
|
||||||
|
attr_t attr = attr_none();
|
||||||
|
attr.x.color = color;
|
||||||
|
return attr;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
ic_private attr_t attr_update_with( attr_t oldattr, attr_t newattr ) {
|
||||||
|
attr_t attr = oldattr;
|
||||||
|
if (newattr.x.color != IC_COLOR_NONE) { attr.x.color = newattr.x.color; }
|
||||||
|
if (newattr.x.bgcolor != IC_COLOR_NONE) { attr.x.bgcolor = newattr.x.bgcolor; }
|
||||||
|
if (newattr.x.bold != IC_NONE) { attr.x.bold = newattr.x.bold; }
|
||||||
|
if (newattr.x.italic != IC_NONE) { attr.x.italic = newattr.x.italic; }
|
||||||
|
if (newattr.x.reverse != IC_NONE) { attr.x.reverse = newattr.x.reverse; }
|
||||||
|
if (newattr.x.underline != IC_NONE) { attr.x.underline = newattr.x.underline; }
|
||||||
|
return attr;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool sgr_is_digit(char c) {
|
||||||
|
return (c >= '0' && c <= '9');
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool sgr_is_sep( char c ) {
|
||||||
|
return (c==';' || c==':');
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool sgr_next_par(const char* s, ssize_t* pi, ssize_t* par) {
|
||||||
|
const ssize_t i = *pi;
|
||||||
|
ssize_t n = 0;
|
||||||
|
while( sgr_is_digit(s[i+n])) {
|
||||||
|
n++;
|
||||||
|
}
|
||||||
|
if (n==0) {
|
||||||
|
*par = 0;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
*pi = i+n;
|
||||||
|
return ic_atoz(s+i, par);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool sgr_next_par3(const char* s, ssize_t* pi, ssize_t* p1, ssize_t* p2, ssize_t* p3) {
|
||||||
|
bool ok = false;
|
||||||
|
ssize_t i = *pi;
|
||||||
|
if (sgr_next_par(s,&i,p1) && sgr_is_sep(s[i])) {
|
||||||
|
i++;
|
||||||
|
if (sgr_next_par(s,&i,p2) && sgr_is_sep(s[i])) {
|
||||||
|
i++;
|
||||||
|
if (sgr_next_par(s,&i,p3)) {
|
||||||
|
ok = true;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*pi = i;
|
||||||
|
return ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
ic_private attr_t attr_from_sgr( const char* s, ssize_t len) {
|
||||||
|
attr_t attr = attr_none();
|
||||||
|
for( ssize_t i = 0; i < len && s[i] != 0; i++) {
|
||||||
|
ssize_t cmd = 0;
|
||||||
|
if (!sgr_next_par(s,&i,&cmd)) continue;
|
||||||
|
switch(cmd) {
|
||||||
|
case 0: attr = attr_default(); break;
|
||||||
|
case 1: attr.x.bold = IC_ON; break;
|
||||||
|
case 3: attr.x.italic = IC_ON; break;
|
||||||
|
case 4: attr.x.underline = IC_ON; break;
|
||||||
|
case 7: attr.x.reverse = IC_ON; break;
|
||||||
|
case 22: attr.x.bold = IC_OFF; break;
|
||||||
|
case 23: attr.x.italic = IC_OFF; break;
|
||||||
|
case 24: attr.x.underline = IC_OFF; break;
|
||||||
|
case 27: attr.x.reverse = IC_OFF; break;
|
||||||
|
case 39: attr.x.color = IC_ANSI_DEFAULT; break;
|
||||||
|
case 49: attr.x.bgcolor = IC_ANSI_DEFAULT; break;
|
||||||
|
default: {
|
||||||
|
if (cmd >= 30 && cmd <= 37) {
|
||||||
|
attr.x.color = IC_ANSI_BLACK + (unsigned)(cmd - 30);
|
||||||
|
}
|
||||||
|
else if (cmd >= 40 && cmd <= 47) {
|
||||||
|
attr.x.bgcolor = IC_ANSI_BLACK + (unsigned)(cmd - 40);
|
||||||
|
}
|
||||||
|
else if (cmd >= 90 && cmd <= 97) {
|
||||||
|
attr.x.color = IC_ANSI_DARKGRAY + (unsigned)(cmd - 90);
|
||||||
|
}
|
||||||
|
else if (cmd >= 100 && cmd <= 107) {
|
||||||
|
attr.x.bgcolor = IC_ANSI_DARKGRAY + (unsigned)(cmd - 100);
|
||||||
|
}
|
||||||
|
else if ((cmd == 38 || cmd == 48) && sgr_is_sep(s[i])) {
|
||||||
|
// non-associative SGR :-(
|
||||||
|
ssize_t par = 0;
|
||||||
|
i++;
|
||||||
|
if (sgr_next_par(s, &i, &par)) {
|
||||||
|
if (par==5 && sgr_is_sep(s[i])) {
|
||||||
|
// ansi 256 index
|
||||||
|
i++;
|
||||||
|
if (sgr_next_par(s, &i, &par) && par >= 0 && par <= 0xFF) {
|
||||||
|
ic_color_t color = color_from_ansi256(par);
|
||||||
|
if (cmd==38) { attr.x.color = color; }
|
||||||
|
else { attr.x.bgcolor = color; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (par == 2 && sgr_is_sep(s[i])) {
|
||||||
|
// rgb value
|
||||||
|
i++;
|
||||||
|
ssize_t r,g,b;
|
||||||
|
if (sgr_next_par3(s, &i, &r,&g,&b)) {
|
||||||
|
ic_color_t color = ic_rgbx(r,g,b);
|
||||||
|
if (cmd==38) { attr.x.color = color; }
|
||||||
|
else { attr.x.bgcolor = color; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
debug_msg("attr: unknow ANSI SGR code: %zd\n", cmd );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return attr;
|
||||||
|
}
|
||||||
|
|
||||||
|
ic_private attr_t attr_from_esc_sgr( const char* s, ssize_t len) {
|
||||||
|
if (len <= 2 || s[0] != '\x1B' || s[1] != '[' || s[len-1] != 'm') return attr_none();
|
||||||
|
return attr_from_sgr(s+2, len-2);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//-------------------------------------------------------------
|
||||||
|
// Attribute buffer
|
||||||
|
//-------------------------------------------------------------
|
||||||
|
struct attrbuf_s {
|
||||||
|
attr_t* attrs;
|
||||||
|
ssize_t capacity;
|
||||||
|
ssize_t count;
|
||||||
|
alloc_t* mem;
|
||||||
|
};
|
||||||
|
|
||||||
|
static bool attrbuf_ensure_capacity( attrbuf_t* ab, ssize_t needed ) {
|
||||||
|
if (needed <= ab->capacity) return true;
|
||||||
|
ssize_t newcap = (ab->capacity <= 0 ? 240 : (ab->capacity > 1000 ? ab->capacity + 1000 : 2*ab->capacity));
|
||||||
|
if (needed > newcap) { newcap = needed; }
|
||||||
|
attr_t* newattrs = mem_realloc_tp( ab->mem, attr_t, ab->attrs, newcap );
|
||||||
|
if (newattrs == NULL) return false;
|
||||||
|
ab->attrs = newattrs;
|
||||||
|
ab->capacity = newcap;
|
||||||
|
assert(needed <= ab->capacity);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool attrbuf_ensure_extra( attrbuf_t* ab, ssize_t extra ) {
|
||||||
|
const ssize_t needed = ab->count + extra;
|
||||||
|
return attrbuf_ensure_capacity( ab, needed );
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
ic_private attrbuf_t* attrbuf_new( alloc_t* mem ) {
|
||||||
|
attrbuf_t* ab = mem_zalloc_tp(mem,attrbuf_t);
|
||||||
|
if (ab == NULL) return NULL;
|
||||||
|
ab->mem = mem;
|
||||||
|
attrbuf_ensure_extra(ab,1);
|
||||||
|
return ab;
|
||||||
|
}
|
||||||
|
|
||||||
|
ic_private void attrbuf_free( attrbuf_t* ab ) {
|
||||||
|
if (ab==NULL) return;
|
||||||
|
mem_free(ab->mem, ab->attrs);
|
||||||
|
mem_free(ab->mem, ab);
|
||||||
|
}
|
||||||
|
|
||||||
|
ic_private void attrbuf_clear(attrbuf_t* ab) {
|
||||||
|
if (ab == NULL) return;
|
||||||
|
ab->count = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
ic_private ssize_t attrbuf_len( attrbuf_t* ab ) {
|
||||||
|
return (ab==NULL ? 0 : ab->count);
|
||||||
|
}
|
||||||
|
|
||||||
|
ic_private const attr_t* attrbuf_attrs( attrbuf_t* ab, ssize_t expected_len ) {
|
||||||
|
assert(expected_len <= ab->count );
|
||||||
|
// expand if needed
|
||||||
|
if (ab->count < expected_len) {
|
||||||
|
if (!attrbuf_ensure_capacity(ab,expected_len)) return NULL;
|
||||||
|
for(ssize_t i = ab->count; i < expected_len; i++) {
|
||||||
|
ab->attrs[i] = attr_none();
|
||||||
|
}
|
||||||
|
ab->count = expected_len;
|
||||||
|
}
|
||||||
|
return ab->attrs;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
static void attrbuf_update_set_at( attrbuf_t* ab, ssize_t pos, ssize_t count, attr_t attr, bool update ) {
|
||||||
|
const ssize_t end = pos + count;
|
||||||
|
if (!attrbuf_ensure_capacity(ab, end)) return;
|
||||||
|
ssize_t i;
|
||||||
|
// initialize if end is beyond the count (todo: avoid duplicate init and set if update==false?)
|
||||||
|
if (ab->count < end) {
|
||||||
|
for(i = ab->count; i < end; i++) {
|
||||||
|
ab->attrs[i] = attr_none();
|
||||||
|
}
|
||||||
|
ab->count = end;
|
||||||
|
}
|
||||||
|
// fill pos to end with attr
|
||||||
|
for(i = pos; i < end; i++) {
|
||||||
|
ab->attrs[i] = (update ? attr_update_with(ab->attrs[i],attr) : attr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ic_private void attrbuf_set_at( attrbuf_t* ab, ssize_t pos, ssize_t count, attr_t attr ) {
|
||||||
|
attrbuf_update_set_at(ab, pos, count, attr, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
ic_private void attrbuf_update_at( attrbuf_t* ab, ssize_t pos, ssize_t count, attr_t attr ) {
|
||||||
|
attrbuf_update_set_at(ab, pos, count, attr, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
ic_private void attrbuf_insert_at( attrbuf_t* ab, ssize_t pos, ssize_t count, attr_t attr ) {
|
||||||
|
if (pos < 0 || pos > ab->count || count <= 0) return;
|
||||||
|
if (!attrbuf_ensure_extra(ab,count)) return;
|
||||||
|
ic_memmove( ab->attrs + pos + count, ab->attrs + pos, (ab->count - pos)*ssizeof(attr_t) );
|
||||||
|
ab->count += count;
|
||||||
|
attrbuf_set_at( ab, pos, count, attr );
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// note: must allow ab == NULL!
|
||||||
|
ic_private ssize_t attrbuf_append_n( stringbuf_t* sb, attrbuf_t* ab, const char* s, ssize_t len, attr_t attr ) {
|
||||||
|
if (s == NULL || len == 0) return sbuf_len(sb);
|
||||||
|
if (ab != NULL) {
|
||||||
|
if (!attrbuf_ensure_extra(ab,len)) return sbuf_len(sb);
|
||||||
|
attrbuf_set_at(ab, ab->count, len, attr);
|
||||||
|
}
|
||||||
|
return sbuf_append_n(sb,s,len);
|
||||||
|
}
|
||||||
|
|
||||||
|
ic_private attr_t attrbuf_attr_at( attrbuf_t* ab, ssize_t pos ) {
|
||||||
|
if (ab==NULL || pos < 0 || pos > ab->count) return attr_none();
|
||||||
|
return ab->attrs[pos];
|
||||||
|
}
|
||||||
|
|
||||||
|
ic_private void attrbuf_delete_at( attrbuf_t* ab, ssize_t pos, ssize_t count ) {
|
||||||
|
if (ab==NULL || pos < 0 || pos > ab->count) return;
|
||||||
|
if (pos + count > ab->count) { count = ab->count - pos; }
|
||||||
|
if (count == 0) return;
|
||||||
|
assert(pos + count <= ab->count);
|
||||||
|
ic_memmove( ab->attrs + pos, ab->attrs + pos + count, ab->count - (pos + count) );
|
||||||
|
ab->count -= count;
|
||||||
|
}
|
70
deps/isocline/src/attr.h
vendored
Normal file
70
deps/isocline/src/attr.h
vendored
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
/* ----------------------------------------------------------------------------
|
||||||
|
Copyright (c) 2021, Daan Leijen
|
||||||
|
This is free software; you can redistribute it and/or modify it
|
||||||
|
under the terms of the MIT License. A copy of the license can be
|
||||||
|
found in the "LICENSE" file at the root of this distribution.
|
||||||
|
-----------------------------------------------------------------------------*/
|
||||||
|
#pragma once
|
||||||
|
#ifndef IC_ATTR_H
|
||||||
|
#define IC_ATTR_H
|
||||||
|
|
||||||
|
#include "common.h"
|
||||||
|
#include "stringbuf.h"
|
||||||
|
|
||||||
|
//-------------------------------------------------------------
|
||||||
|
// text attributes
|
||||||
|
//-------------------------------------------------------------
|
||||||
|
|
||||||
|
#define IC_ON (1)
|
||||||
|
#define IC_OFF (-1)
|
||||||
|
#define IC_NONE (0)
|
||||||
|
|
||||||
|
// try to fit in 64 bits
|
||||||
|
// note: order is important for some compilers
|
||||||
|
// note: each color can actually be 25 bits
|
||||||
|
typedef union attr_s {
|
||||||
|
struct {
|
||||||
|
unsigned int color:28;
|
||||||
|
signed int bold:2;
|
||||||
|
signed int reverse:2;
|
||||||
|
unsigned int bgcolor:28;
|
||||||
|
signed int underline:2;
|
||||||
|
signed int italic:2;
|
||||||
|
} x;
|
||||||
|
uint64_t value;
|
||||||
|
} attr_t;
|
||||||
|
|
||||||
|
ic_private attr_t attr_none(void);
|
||||||
|
ic_private attr_t attr_default(void);
|
||||||
|
ic_private attr_t attr_from_color( ic_color_t color );
|
||||||
|
|
||||||
|
ic_private bool attr_is_none(attr_t attr);
|
||||||
|
ic_private bool attr_is_eq(attr_t attr1, attr_t attr2);
|
||||||
|
|
||||||
|
ic_private attr_t attr_update_with( attr_t attr, attr_t newattr );
|
||||||
|
|
||||||
|
ic_private attr_t attr_from_sgr( const char* s, ssize_t len);
|
||||||
|
ic_private attr_t attr_from_esc_sgr( const char* s, ssize_t len);
|
||||||
|
|
||||||
|
//-------------------------------------------------------------
|
||||||
|
// attribute buffer used for rich rendering
|
||||||
|
//-------------------------------------------------------------
|
||||||
|
|
||||||
|
struct attrbuf_s;
|
||||||
|
typedef struct attrbuf_s attrbuf_t;
|
||||||
|
|
||||||
|
ic_private attrbuf_t* attrbuf_new( alloc_t* mem );
|
||||||
|
ic_private void attrbuf_free( attrbuf_t* ab ); // ab can be NULL
|
||||||
|
ic_private void attrbuf_clear( attrbuf_t* ab ); // ab can be NULL
|
||||||
|
ic_private ssize_t attrbuf_len( attrbuf_t* ab); // ab can be NULL
|
||||||
|
ic_private const attr_t* attrbuf_attrs( attrbuf_t* ab, ssize_t expected_len );
|
||||||
|
ic_private ssize_t attrbuf_append_n( stringbuf_t* sb, attrbuf_t* ab, const char* s, ssize_t len, attr_t attr );
|
||||||
|
|
||||||
|
ic_private void attrbuf_set_at( attrbuf_t* ab, ssize_t pos, ssize_t count, attr_t attr );
|
||||||
|
ic_private void attrbuf_update_at( attrbuf_t* ab, ssize_t pos, ssize_t count, attr_t attr );
|
||||||
|
ic_private void attrbuf_insert_at( attrbuf_t* ab, ssize_t pos, ssize_t count, attr_t attr );
|
||||||
|
|
||||||
|
ic_private attr_t attrbuf_attr_at( attrbuf_t* ab, ssize_t pos );
|
||||||
|
ic_private void attrbuf_delete_at( attrbuf_t* ab, ssize_t pos, ssize_t count );
|
||||||
|
|
||||||
|
#endif // IC_ATTR_H
|
842
deps/isocline/src/bbcode.c
vendored
Normal file
842
deps/isocline/src/bbcode.c
vendored
Normal file
@ -0,0 +1,842 @@
|
|||||||
|
/* ----------------------------------------------------------------------------
|
||||||
|
Copyright (c) 2021, Daan Leijen
|
||||||
|
This is free software; you can redistribute it and/or modify it
|
||||||
|
under the terms of the MIT License. A copy of the license can be
|
||||||
|
found in the "LICENSE" file at the root of this distribution.
|
||||||
|
-----------------------------------------------------------------------------*/
|
||||||
|
#include <string.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdarg.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
#include "common.h"
|
||||||
|
#include "attr.h"
|
||||||
|
#include "term.h"
|
||||||
|
#include "bbcode.h"
|
||||||
|
|
||||||
|
//-------------------------------------------------------------
|
||||||
|
// HTML color table
|
||||||
|
//-------------------------------------------------------------
|
||||||
|
|
||||||
|
#include "bbcode_colors.c"
|
||||||
|
|
||||||
|
//-------------------------------------------------------------
|
||||||
|
// Types
|
||||||
|
//-------------------------------------------------------------
|
||||||
|
|
||||||
|
typedef struct style_s {
|
||||||
|
const char* name; // name of the style
|
||||||
|
attr_t attr; // attribute to apply
|
||||||
|
} style_t;
|
||||||
|
|
||||||
|
typedef enum align_e {
|
||||||
|
IC_ALIGN_LEFT,
|
||||||
|
IC_ALIGN_CENTER,
|
||||||
|
IC_ALIGN_RIGHT
|
||||||
|
} align_t;
|
||||||
|
|
||||||
|
typedef struct width_s {
|
||||||
|
ssize_t w; // > 0
|
||||||
|
align_t align;
|
||||||
|
bool dots; // "..." (e.g. "sentence...")
|
||||||
|
char fill; // " " (e.g. "hello ")
|
||||||
|
} width_t;
|
||||||
|
|
||||||
|
typedef struct tag_s {
|
||||||
|
const char* name; // tag open name
|
||||||
|
attr_t attr; // the saved attribute before applying the style
|
||||||
|
width_t width; // start sequence of at most "width" columns
|
||||||
|
ssize_t pos; // start position in the output (used for width restriction)
|
||||||
|
} tag_t;
|
||||||
|
|
||||||
|
|
||||||
|
static void tag_init(tag_t* tag) {
|
||||||
|
memset(tag,0,sizeof(*tag));
|
||||||
|
}
|
||||||
|
|
||||||
|
struct bbcode_s {
|
||||||
|
tag_t* tags; // stack of tags; one entry for each open tag
|
||||||
|
ssize_t tags_capacity;
|
||||||
|
ssize_t tags_nesting;
|
||||||
|
style_t* styles; // list of used defined styles
|
||||||
|
ssize_t styles_capacity;
|
||||||
|
ssize_t styles_count;
|
||||||
|
term_t* term; // terminal
|
||||||
|
alloc_t* mem; // allocator
|
||||||
|
// caches
|
||||||
|
stringbuf_t* out; // print buffer
|
||||||
|
attrbuf_t* out_attrs;
|
||||||
|
stringbuf_t* vout; // vprintf buffer
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
//-------------------------------------------------------------
|
||||||
|
// Create, helpers
|
||||||
|
//-------------------------------------------------------------
|
||||||
|
|
||||||
|
ic_private bbcode_t* bbcode_new( alloc_t* mem, term_t* term ) {
|
||||||
|
bbcode_t* bb = mem_zalloc_tp(mem,bbcode_t);
|
||||||
|
if (bb==NULL) return NULL;
|
||||||
|
bb->mem = mem;
|
||||||
|
bb->term = term;
|
||||||
|
bb->out = sbuf_new(mem);
|
||||||
|
bb->out_attrs = attrbuf_new(mem);
|
||||||
|
bb->vout = sbuf_new(mem);
|
||||||
|
return bb;
|
||||||
|
}
|
||||||
|
|
||||||
|
ic_private void bbcode_free( bbcode_t* bb ) {
|
||||||
|
for(ssize_t i = 0; i < bb->styles_count; i++) {
|
||||||
|
mem_free(bb->mem, bb->styles[i].name);
|
||||||
|
}
|
||||||
|
mem_free(bb->mem, bb->tags);
|
||||||
|
mem_free(bb->mem, bb->styles);
|
||||||
|
sbuf_free(bb->vout);
|
||||||
|
sbuf_free(bb->out);
|
||||||
|
attrbuf_free(bb->out_attrs);
|
||||||
|
mem_free(bb->mem, bb);
|
||||||
|
}
|
||||||
|
|
||||||
|
ic_private void bbcode_style_add( bbcode_t* bb, const char* style_name, attr_t attr ) {
|
||||||
|
if (bb->styles_count >= bb->styles_capacity) {
|
||||||
|
ssize_t newlen = bb->styles_capacity + 32;
|
||||||
|
style_t* p = mem_realloc_tp( bb->mem, style_t, bb->styles, newlen );
|
||||||
|
if (p == NULL) return;
|
||||||
|
bb->styles = p;
|
||||||
|
bb->styles_capacity = newlen;
|
||||||
|
}
|
||||||
|
assert(bb->styles_count < bb->styles_capacity);
|
||||||
|
bb->styles[bb->styles_count].name = mem_strdup( bb->mem, style_name );
|
||||||
|
bb->styles[bb->styles_count].attr = attr;
|
||||||
|
bb->styles_count++;
|
||||||
|
}
|
||||||
|
|
||||||
|
static ssize_t bbcode_tag_push( bbcode_t* bb, const tag_t* tag ) {
|
||||||
|
if (bb->tags_nesting >= bb->tags_capacity) {
|
||||||
|
ssize_t newcap = bb->tags_capacity + 32;
|
||||||
|
tag_t* p = mem_realloc_tp( bb->mem, tag_t, bb->tags, newcap );
|
||||||
|
if (p == NULL) return -1;
|
||||||
|
bb->tags = p;
|
||||||
|
bb->tags_capacity = newcap;
|
||||||
|
}
|
||||||
|
assert(bb->tags_nesting < bb->tags_capacity);
|
||||||
|
bb->tags[bb->tags_nesting] = *tag;
|
||||||
|
bb->tags_nesting++;
|
||||||
|
return (bb->tags_nesting-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void bbcode_tag_pop( bbcode_t* bb, tag_t* tag ) {
|
||||||
|
if (bb->tags_nesting <= 0) {
|
||||||
|
if (tag != NULL) { tag_init(tag); }
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
bb->tags_nesting--;
|
||||||
|
if (tag != NULL) {
|
||||||
|
*tag = bb->tags[bb->tags_nesting];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//-------------------------------------------------------------
|
||||||
|
// Invalid parse/values/balance
|
||||||
|
//-------------------------------------------------------------
|
||||||
|
|
||||||
|
static void bbcode_invalid(const char* fmt, ... ) {
|
||||||
|
if (getenv("ISOCLINE_BBCODE_DEBUG") != NULL) {
|
||||||
|
va_list args;
|
||||||
|
va_start(args,fmt);
|
||||||
|
vfprintf(stderr,fmt,args);
|
||||||
|
va_end(args);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//-------------------------------------------------------------
|
||||||
|
// Set attributes
|
||||||
|
//-------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
static attr_t bbcode_open( bbcode_t* bb, ssize_t out_pos, const tag_t* tag, attr_t current ) {
|
||||||
|
// save current and set
|
||||||
|
tag_t cur;
|
||||||
|
tag_init(&cur);
|
||||||
|
cur.name = tag->name;
|
||||||
|
cur.attr = current;
|
||||||
|
cur.width = tag->width;
|
||||||
|
cur.pos = out_pos;
|
||||||
|
bbcode_tag_push(bb,&cur);
|
||||||
|
return attr_update_with( current, tag->attr );
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool bbcode_close( bbcode_t* bb, ssize_t base, const char* name, tag_t* pprev ) {
|
||||||
|
// pop until match
|
||||||
|
while (bb->tags_nesting > base) {
|
||||||
|
tag_t prev;
|
||||||
|
bbcode_tag_pop(bb,&prev);
|
||||||
|
if (name==NULL || prev.name==NULL || ic_stricmp(prev.name,name) == 0) {
|
||||||
|
// matched
|
||||||
|
if (pprev != NULL) { *pprev = prev; }
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// unbalanced: we either continue popping or restore the tags depending if there is a matching open tag in our tags.
|
||||||
|
bool has_open_tag = false;
|
||||||
|
if (name != NULL) {
|
||||||
|
for( ssize_t i = bb->tags_nesting - 1; i > base; i--) {
|
||||||
|
if (bb->tags[i].name != NULL && ic_stricmp(bb->tags[i].name, name) == 0) {
|
||||||
|
has_open_tag = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
bbcode_invalid("bbcode: unbalanced tags: open [%s], close [/%s]\n", prev.name, name);
|
||||||
|
if (!has_open_tag) {
|
||||||
|
bbcode_tag_push( bb, &prev ); // restore the tags and ignore this close tag
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// continue until we hit our open tag
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (pprev != NULL) { memset(pprev,0,sizeof(*pprev)); }
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
//-------------------------------------------------------------
|
||||||
|
// Update attributes
|
||||||
|
//-------------------------------------------------------------
|
||||||
|
|
||||||
|
static const char* attr_update_bool( const char* fname, signed int* field, const char* value ) {
|
||||||
|
if (value == NULL || value[0] == 0 || strcmp(value,"on") || strcmp(value,"true") || strcmp(value,"1")) {
|
||||||
|
*field = IC_ON;
|
||||||
|
}
|
||||||
|
else if (strcmp(value,"off") || strcmp(value,"false") || strcmp(value,"0")) {
|
||||||
|
*field = IC_OFF;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
bbcode_invalid("bbcode: invalid %s value: %s\n", fname, value );
|
||||||
|
}
|
||||||
|
return fname;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const char* attr_update_color( const char* fname, ic_color_t* field, const char* value ) {
|
||||||
|
if (value == NULL || value[0] == 0 || strcmp(value,"none") == 0) {
|
||||||
|
*field = IC_COLOR_NONE;
|
||||||
|
return fname;
|
||||||
|
}
|
||||||
|
|
||||||
|
// hex value
|
||||||
|
if (value[0] == '#') {
|
||||||
|
uint32_t rgb = 0;
|
||||||
|
if (sscanf(value,"#%x",&rgb) == 1) {
|
||||||
|
*field = ic_rgb(rgb);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
bbcode_invalid("bbcode: invalid color code: %s\n", value);
|
||||||
|
}
|
||||||
|
return fname;
|
||||||
|
}
|
||||||
|
|
||||||
|
// search color names
|
||||||
|
ssize_t lo = 0;
|
||||||
|
ssize_t hi = IC_HTML_COLOR_COUNT-1;
|
||||||
|
while( lo <= hi ) {
|
||||||
|
ssize_t mid = (lo + hi) / 2;
|
||||||
|
style_color_t* info = &html_colors[mid];
|
||||||
|
int cmp = strcmp(info->name,value);
|
||||||
|
if (cmp < 0) {
|
||||||
|
lo = mid+1;
|
||||||
|
}
|
||||||
|
else if (cmp > 0) {
|
||||||
|
hi = mid-1;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
*field = info->color;
|
||||||
|
return fname;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
bbcode_invalid("bbcode: unknown %s: %s\n", fname, value);
|
||||||
|
*field = IC_COLOR_NONE;
|
||||||
|
return fname;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const char* attr_update_sgr( const char* fname, attr_t* attr, const char* value ) {
|
||||||
|
*attr = attr_update_with(*attr, attr_from_sgr(value, ic_strlen(value)));
|
||||||
|
return fname;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void attr_update_width( width_t* pwidth, char default_fill, const char* value ) {
|
||||||
|
// parse width value: <width>;<left|center|right>;<fill>;<cut>
|
||||||
|
width_t width;
|
||||||
|
memset(&width, 0, sizeof(width));
|
||||||
|
width.fill = default_fill; // use 0 for no-fill (as for max-width)
|
||||||
|
if (ic_atoz(value, &width.w)) {
|
||||||
|
ssize_t i = 0;
|
||||||
|
while( value[i] != ';' && value[i] != 0 ) { i++; }
|
||||||
|
if (value[i] == ';') {
|
||||||
|
i++;
|
||||||
|
ssize_t len = 0;
|
||||||
|
while( value[i+len] != ';' && value[i+len] != 0 ) { len++; }
|
||||||
|
if (len == 4 && ic_istarts_with(value+i,"left")) {
|
||||||
|
width.align = IC_ALIGN_LEFT;
|
||||||
|
}
|
||||||
|
else if (len == 5 && ic_istarts_with(value+i,"right")) {
|
||||||
|
width.align = IC_ALIGN_RIGHT;
|
||||||
|
}
|
||||||
|
else if (len == 6 && ic_istarts_with(value+i,"center")) {
|
||||||
|
width.align = IC_ALIGN_CENTER;
|
||||||
|
}
|
||||||
|
i += len;
|
||||||
|
if (value[i] == ';') {
|
||||||
|
i++; len = 0;
|
||||||
|
while( value[i+len] != ';' && value[i+len] != 0 ) { len++; }
|
||||||
|
if (len == 1) { width.fill = value[i]; }
|
||||||
|
i+= len;
|
||||||
|
if (value[i] == ';') {
|
||||||
|
i++; len = 0;
|
||||||
|
while( value[i+len] != ';' && value[i+len] != 0 ) { len++; }
|
||||||
|
if ((len == 2 && ic_istarts_with(value+i,"on")) || (len == 1 && value[i] == '1')) { width.dots = true; }
|
||||||
|
i += len;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
bbcode_invalid("bbcode: illegal width: %s\n", value);
|
||||||
|
}
|
||||||
|
*pwidth = width;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const char* attr_update_ansi_color( const char* fname, ic_color_t* color, const char* value ) {
|
||||||
|
ssize_t num = 0;
|
||||||
|
if (ic_atoz(value, &num) && num >= 0 && num <= 256) {
|
||||||
|
*color = color_from_ansi256(num);
|
||||||
|
}
|
||||||
|
return fname;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static const char* attr_update_property( tag_t* tag, const char* attr_name, const char* value ) {
|
||||||
|
const char* fname = NULL;
|
||||||
|
fname = "bold";
|
||||||
|
if (strcmp(attr_name,fname) == 0) {
|
||||||
|
signed int b = IC_NONE;
|
||||||
|
attr_update_bool(fname,&b, value);
|
||||||
|
if (b != IC_NONE) { tag->attr.x.bold = b; }
|
||||||
|
return fname;
|
||||||
|
}
|
||||||
|
fname = "italic";
|
||||||
|
if (strcmp(attr_name,fname) == 0) {
|
||||||
|
signed int b = IC_NONE;
|
||||||
|
attr_update_bool(fname,&b, value);
|
||||||
|
if (b != IC_NONE) { tag->attr.x.italic = b; }
|
||||||
|
return fname;
|
||||||
|
}
|
||||||
|
fname = "underline";
|
||||||
|
if (strcmp(attr_name,fname) == 0) {
|
||||||
|
signed int b = IC_NONE;
|
||||||
|
attr_update_bool(fname,&b, value);
|
||||||
|
if (b != IC_NONE) { tag->attr.x.underline = b; }
|
||||||
|
return fname;
|
||||||
|
}
|
||||||
|
fname = "reverse";
|
||||||
|
if (strcmp(attr_name,fname) == 0) {
|
||||||
|
signed int b = IC_NONE;
|
||||||
|
attr_update_bool(fname,&b, value);
|
||||||
|
if (b != IC_NONE) { tag->attr.x.reverse = b; }
|
||||||
|
return fname;
|
||||||
|
}
|
||||||
|
fname = "color";
|
||||||
|
if (strcmp(attr_name,fname) == 0) {
|
||||||
|
unsigned int color = IC_COLOR_NONE;
|
||||||
|
attr_update_color(fname, &color, value);
|
||||||
|
if (color != IC_COLOR_NONE) { tag->attr.x.color = color; }
|
||||||
|
return fname;
|
||||||
|
}
|
||||||
|
fname = "bgcolor";
|
||||||
|
if (strcmp(attr_name,fname) == 0) {
|
||||||
|
unsigned int color = IC_COLOR_NONE;
|
||||||
|
attr_update_color(fname, &color, value);
|
||||||
|
if (color != IC_COLOR_NONE) { tag->attr.x.bgcolor = color; }
|
||||||
|
return fname;
|
||||||
|
}
|
||||||
|
fname = "ansi-sgr";
|
||||||
|
if (strcmp(attr_name,fname) == 0) {
|
||||||
|
attr_update_sgr(fname, &tag->attr, value);
|
||||||
|
return fname;
|
||||||
|
}
|
||||||
|
fname = "ansi-color";
|
||||||
|
if (strcmp(attr_name,fname) == 0) {
|
||||||
|
ic_color_t color = IC_COLOR_NONE;;
|
||||||
|
attr_update_ansi_color(fname, &color, value);
|
||||||
|
if (color != IC_COLOR_NONE) { tag->attr.x.color = color; }
|
||||||
|
return fname;
|
||||||
|
}
|
||||||
|
fname = "ansi-bgcolor";
|
||||||
|
if (strcmp(attr_name,fname) == 0) {
|
||||||
|
ic_color_t color = IC_COLOR_NONE;;
|
||||||
|
attr_update_ansi_color(fname, &color, value);
|
||||||
|
if (color != IC_COLOR_NONE) { tag->attr.x.bgcolor = color; }
|
||||||
|
return fname;
|
||||||
|
}
|
||||||
|
fname = "width";
|
||||||
|
if (strcmp(attr_name,fname) == 0) {
|
||||||
|
attr_update_width(&tag->width, ' ', value);
|
||||||
|
return fname;
|
||||||
|
}
|
||||||
|
fname = "max-width";
|
||||||
|
if (strcmp(attr_name,fname) == 0) {
|
||||||
|
attr_update_width(&tag->width, 0, value);
|
||||||
|
return "width";
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static const style_t builtin_styles[] = {
|
||||||
|
{ "b", { { IC_COLOR_NONE, IC_ON , IC_NONE, IC_COLOR_NONE, IC_NONE, IC_NONE } } },
|
||||||
|
{ "r", { { IC_COLOR_NONE, IC_NONE, IC_ON , IC_COLOR_NONE, IC_NONE, IC_NONE } } },
|
||||||
|
{ "u", { { IC_COLOR_NONE, IC_NONE, IC_NONE, IC_COLOR_NONE, IC_ON , IC_NONE } } },
|
||||||
|
{ "i", { { IC_COLOR_NONE, IC_NONE, IC_NONE, IC_COLOR_NONE, IC_NONE, IC_ON } } },
|
||||||
|
{ "em", { { IC_COLOR_NONE, IC_ON , IC_NONE, IC_COLOR_NONE, IC_NONE, IC_NONE } } }, // bold
|
||||||
|
{ "url",{ { IC_COLOR_NONE, IC_NONE, IC_NONE, IC_COLOR_NONE, IC_ON, IC_NONE } } }, // underline
|
||||||
|
{ NULL, { { IC_COLOR_NONE, IC_NONE, IC_NONE, IC_COLOR_NONE, IC_NONE, IC_NONE } } }
|
||||||
|
};
|
||||||
|
|
||||||
|
static void attr_update_with_styles( tag_t* tag, const char* attr_name, const char* value,
|
||||||
|
bool usebgcolor, const style_t* styles, ssize_t count )
|
||||||
|
{
|
||||||
|
// direct hex color?
|
||||||
|
if (attr_name[0] == '#' && (value == NULL || value[0]==0)) {
|
||||||
|
value = attr_name;
|
||||||
|
attr_name = (usebgcolor ? "bgcolor" : "color");
|
||||||
|
}
|
||||||
|
// first try if it is a builtin property
|
||||||
|
const char* name;
|
||||||
|
if ((name = attr_update_property(tag,attr_name,value)) != NULL) {
|
||||||
|
if (tag->name != NULL) tag->name = name;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// then check all styles
|
||||||
|
while( count-- > 0 ) {
|
||||||
|
const style_t* style = styles + count;
|
||||||
|
if (strcmp(style->name,attr_name) == 0) {
|
||||||
|
tag->attr = attr_update_with(tag->attr,style->attr);
|
||||||
|
if (tag->name != NULL) tag->name = style->name;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// check builtin styles; todo: binary search?
|
||||||
|
for( const style_t* style = builtin_styles; style->name != NULL; style++) {
|
||||||
|
if (strcmp(style->name,attr_name) == 0) {
|
||||||
|
tag->attr = attr_update_with(tag->attr,style->attr);
|
||||||
|
if (tag->name != NULL) tag->name = style->name;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// check colors as a style
|
||||||
|
ssize_t lo = 0;
|
||||||
|
ssize_t hi = IC_HTML_COLOR_COUNT-1;
|
||||||
|
while( lo <= hi ) {
|
||||||
|
ssize_t mid = (lo + hi) / 2;
|
||||||
|
style_color_t* info = &html_colors[mid];
|
||||||
|
int cmp = strcmp(info->name,attr_name);
|
||||||
|
if (cmp < 0) {
|
||||||
|
lo = mid+1;
|
||||||
|
}
|
||||||
|
else if (cmp > 0) {
|
||||||
|
hi = mid-1;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
attr_t cattr = attr_none();
|
||||||
|
if (usebgcolor) { cattr.x.bgcolor = info->color; }
|
||||||
|
else { cattr.x.color = info->color; }
|
||||||
|
tag->attr = attr_update_with(tag->attr,cattr);
|
||||||
|
if (tag->name != NULL) tag->name = info->name;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// not found
|
||||||
|
bbcode_invalid("bbcode: unknown style: %s\n", attr_name);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
ic_private attr_t bbcode_style( bbcode_t* bb, const char* style_name ) {
|
||||||
|
tag_t tag;
|
||||||
|
tag_init(&tag);
|
||||||
|
attr_update_with_styles( &tag, style_name, NULL, false, bb->styles, bb->styles_count );
|
||||||
|
return tag.attr;
|
||||||
|
}
|
||||||
|
|
||||||
|
//-------------------------------------------------------------
|
||||||
|
// Parse tags
|
||||||
|
//-------------------------------------------------------------
|
||||||
|
|
||||||
|
ic_private const char* parse_skip_white(const char* s) {
|
||||||
|
while( *s != 0 && *s != ']') {
|
||||||
|
if (!(*s == ' ' || *s == '\t' || *s == '\n' || *s == '\r')) break;
|
||||||
|
s++;
|
||||||
|
}
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
ic_private const char* parse_skip_to_white(const char* s) {
|
||||||
|
while( *s != 0 && *s != ']') {
|
||||||
|
if (*s == ' ' || *s == '\t' || *s == '\n' || *s == '\r') break;
|
||||||
|
s++;
|
||||||
|
}
|
||||||
|
return parse_skip_white(s);
|
||||||
|
}
|
||||||
|
|
||||||
|
ic_private const char* parse_skip_to_end(const char* s) {
|
||||||
|
while( *s != 0 && *s != ']' ) { s++; }
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
ic_private const char* parse_attr_name(const char* s) {
|
||||||
|
if (*s == '#') {
|
||||||
|
s++; // hex rgb color as id
|
||||||
|
while( *s != 0 && *s != ']') {
|
||||||
|
if (!((*s >= 'a' && *s <= 'f') || (*s >= 'A' && *s <= 'Z') || (*s >= '0' && *s <= '9'))) break;
|
||||||
|
s++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
while( *s != 0 && *s != ']') {
|
||||||
|
if (!((*s >= 'a' && *s <= 'z') || (*s >= 'A' && *s <= 'Z') ||
|
||||||
|
(*s >= '0' && *s <= '9') || *s == '_' || *s == '-')) break;
|
||||||
|
s++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
ic_private const char* parse_value(const char* s, const char** start, const char** end) {
|
||||||
|
if (*s == '"') {
|
||||||
|
s++;
|
||||||
|
*start = s;
|
||||||
|
while( *s != 0 ) {
|
||||||
|
if (*s == '"') break;
|
||||||
|
s++;
|
||||||
|
}
|
||||||
|
*end = s;
|
||||||
|
if (*s == '"') { s++; }
|
||||||
|
}
|
||||||
|
else if (*s == '#') {
|
||||||
|
*start = s;
|
||||||
|
s++;
|
||||||
|
while( *s != 0 ) {
|
||||||
|
if (!((*s >= 'a' && *s <= 'f') || (*s >= 'A' && *s <= 'Z') || (*s >= '0' && *s <= '9'))) break;
|
||||||
|
s++;
|
||||||
|
}
|
||||||
|
*end = s;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
*start = s;
|
||||||
|
while( *s != 0 ) {
|
||||||
|
if (!((*s >= 'a' && *s <= 'z') || (*s >= 'A' && *s <= 'F') || (*s >= '0' && *s <= '9') || *s == '-' || *s == '_')) break;
|
||||||
|
s++;
|
||||||
|
}
|
||||||
|
*end = s;
|
||||||
|
}
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
ic_private const char* parse_tag_value( tag_t* tag, char* idbuf, const char* s, const style_t* styles, ssize_t scount ) {
|
||||||
|
// parse: \s*[\w-]+\s*(=\s*<value>)
|
||||||
|
bool usebgcolor = false;
|
||||||
|
const char* id = s;
|
||||||
|
const char* idend = parse_attr_name(id);
|
||||||
|
const char* val = NULL;
|
||||||
|
const char* valend = NULL;
|
||||||
|
if (id == idend) {
|
||||||
|
bbcode_invalid("bbcode: empty identifier? %.10s...\n", id );
|
||||||
|
return parse_skip_to_white(id);
|
||||||
|
}
|
||||||
|
// "on" bgcolor?
|
||||||
|
s = parse_skip_white(idend);
|
||||||
|
if (idend - id == 2 && ic_strnicmp(id,"on",2) == 0 && *s != '=') {
|
||||||
|
usebgcolor = true;
|
||||||
|
id = s;
|
||||||
|
idend = parse_attr_name(id);
|
||||||
|
if (id == idend) {
|
||||||
|
bbcode_invalid("bbcode: empty identifier follows 'on'? %.10s...\n", id );
|
||||||
|
return parse_skip_to_white(id);
|
||||||
|
}
|
||||||
|
s = parse_skip_white(idend);
|
||||||
|
}
|
||||||
|
// value
|
||||||
|
if (*s == '=') {
|
||||||
|
s++;
|
||||||
|
s = parse_skip_white(s);
|
||||||
|
s = parse_value(s, &val, &valend);
|
||||||
|
s = parse_skip_white(s);
|
||||||
|
}
|
||||||
|
// limit name and attr to 128 bytes
|
||||||
|
char valbuf[128];
|
||||||
|
ic_strncpy( idbuf, 128, id, idend - id);
|
||||||
|
ic_strncpy( valbuf, 128, val, valend - val);
|
||||||
|
ic_str_tolower(idbuf);
|
||||||
|
ic_str_tolower(valbuf);
|
||||||
|
attr_update_with_styles( tag, idbuf, valbuf, usebgcolor, styles, scount );
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const char* parse_tag_values( tag_t* tag, char* idbuf, const char* s, const style_t* styles, ssize_t scount ) {
|
||||||
|
s = parse_skip_white(s);
|
||||||
|
idbuf[0] = 0;
|
||||||
|
ssize_t count = 0;
|
||||||
|
while( *s != 0 && *s != ']') {
|
||||||
|
char idbuf_next[128];
|
||||||
|
s = parse_tag_value(tag, (count==0 ? idbuf : idbuf_next), s, styles, scount);
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
if (*s == ']') { s++; }
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const char* parse_tag( tag_t* tag, char* idbuf, bool* open, bool* pre, const char* s, const style_t* styles, ssize_t scount ) {
|
||||||
|
*open = true;
|
||||||
|
*pre = false;
|
||||||
|
if (*s != '[') return s;
|
||||||
|
s = parse_skip_white(s+1);
|
||||||
|
if (*s == '!') { // pre
|
||||||
|
*pre = true;
|
||||||
|
s = parse_skip_white(s+1);
|
||||||
|
}
|
||||||
|
else if (*s == '/') {
|
||||||
|
*open = false;
|
||||||
|
s = parse_skip_white(s+1);
|
||||||
|
};
|
||||||
|
s = parse_tag_values( tag, idbuf, s, styles, scount);
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//---------------------------------------------------------
|
||||||
|
// Styles
|
||||||
|
//---------------------------------------------------------
|
||||||
|
|
||||||
|
static void bbcode_parse_tag_content( bbcode_t* bb, const char* s, tag_t* tag ) {
|
||||||
|
tag_init(tag);
|
||||||
|
if (s != NULL) {
|
||||||
|
char idbuf[128];
|
||||||
|
parse_tag_values(tag, idbuf, s, bb->styles, bb->styles_count);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ic_private void bbcode_style_def( bbcode_t* bb, const char* style_name, const char* s ) {
|
||||||
|
tag_t tag;
|
||||||
|
bbcode_parse_tag_content( bb, s, &tag);
|
||||||
|
bbcode_style_add(bb, style_name, tag.attr);
|
||||||
|
}
|
||||||
|
|
||||||
|
ic_private void bbcode_style_open( bbcode_t* bb, const char* fmt ) {
|
||||||
|
tag_t tag;
|
||||||
|
bbcode_parse_tag_content(bb, fmt, &tag);
|
||||||
|
term_set_attr( bb->term, bbcode_open(bb, 0, &tag, term_get_attr(bb->term)) );
|
||||||
|
}
|
||||||
|
|
||||||
|
ic_private void bbcode_style_close( bbcode_t* bb, const char* fmt ) {
|
||||||
|
const ssize_t base = bb->tags_nesting - 1; // as we end a style
|
||||||
|
tag_t tag;
|
||||||
|
bbcode_parse_tag_content(bb, fmt, &tag);
|
||||||
|
tag_t prev;
|
||||||
|
if (bbcode_close(bb, base, tag.name, &prev)) {
|
||||||
|
term_set_attr( bb->term, prev.attr );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//---------------------------------------------------------
|
||||||
|
// Restrict to width
|
||||||
|
//---------------------------------------------------------
|
||||||
|
|
||||||
|
static void bbcode_restrict_width( ssize_t start, width_t width, stringbuf_t* out, attrbuf_t* attr_out ) {
|
||||||
|
if (width.w <= 0) return;
|
||||||
|
assert(start <= sbuf_len(out));
|
||||||
|
assert(attr_out == NULL || sbuf_len(out) == attrbuf_len(attr_out));
|
||||||
|
const char* s = sbuf_string(out) + start;
|
||||||
|
const ssize_t len = sbuf_len(out) - start;
|
||||||
|
const ssize_t w = str_column_width(s);
|
||||||
|
if (w == width.w) return; // fits exactly
|
||||||
|
if (w > width.w) {
|
||||||
|
// too large
|
||||||
|
ssize_t innerw = (width.dots && width.w > 3 ? width.w-3 : width.w);
|
||||||
|
if (width.align == IC_ALIGN_RIGHT) {
|
||||||
|
// right align
|
||||||
|
const ssize_t ndel = str_skip_until_fit( s, innerw );
|
||||||
|
sbuf_delete_at( out, start, ndel );
|
||||||
|
attrbuf_delete_at( attr_out, start, ndel );
|
||||||
|
if (innerw < width.w) {
|
||||||
|
// add dots
|
||||||
|
sbuf_insert_at( out, "...", start );
|
||||||
|
attr_t attr = attrbuf_attr_at(attr_out, start);
|
||||||
|
attrbuf_insert_at( attr_out, start, 3, attr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// left or center align
|
||||||
|
ssize_t count = str_take_while_fit( s, innerw );
|
||||||
|
sbuf_delete_at( out, start + count, len - count );
|
||||||
|
attrbuf_delete_at( attr_out, start + count, len - count );
|
||||||
|
if (innerw < width.w) {
|
||||||
|
// add dots
|
||||||
|
attr_t attr = attrbuf_attr_at(attr_out,start);
|
||||||
|
attrbuf_append_n( out, attr_out, "...", 3, attr );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// too short, pad to width
|
||||||
|
const ssize_t diff = (width.w - w);
|
||||||
|
const ssize_t pad_left = (width.align == IC_ALIGN_RIGHT ? diff : (width.align == IC_ALIGN_LEFT ? 0 : diff / 2));
|
||||||
|
const ssize_t pad_right = (width.align == IC_ALIGN_LEFT ? diff : (width.align == IC_ALIGN_RIGHT ? 0 : diff - pad_left));
|
||||||
|
if (width.fill != 0 && pad_left > 0) {
|
||||||
|
const attr_t attr = attrbuf_attr_at(attr_out,start);
|
||||||
|
for( ssize_t i = 0; i < pad_left; i++) { // todo: optimize
|
||||||
|
sbuf_insert_char_at(out, width.fill, start);
|
||||||
|
}
|
||||||
|
attrbuf_insert_at( attr_out, start, pad_left, attr );
|
||||||
|
}
|
||||||
|
if (width.fill != 0 && pad_right > 0) {
|
||||||
|
const attr_t attr = attrbuf_attr_at(attr_out,sbuf_len(out) - 1);
|
||||||
|
char buf[2];
|
||||||
|
buf[0] = width.fill;
|
||||||
|
buf[1] = 0;
|
||||||
|
for( ssize_t i = 0; i < pad_right; i++) { // todo: optimize
|
||||||
|
attrbuf_append_n( out, attr_out, buf, 1, attr );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//---------------------------------------------------------
|
||||||
|
// Print
|
||||||
|
//---------------------------------------------------------
|
||||||
|
|
||||||
|
ic_private ssize_t bbcode_process_tag( bbcode_t* bb, const char* s, const ssize_t nesting_base,
|
||||||
|
stringbuf_t* out, attrbuf_t* attr_out, attr_t* cur_attr ) {
|
||||||
|
assert(*s == '[');
|
||||||
|
tag_t tag;
|
||||||
|
tag_init(&tag);
|
||||||
|
bool open = true;
|
||||||
|
bool ispre = false;
|
||||||
|
char idbuf[128];
|
||||||
|
const char* end = parse_tag( &tag, idbuf, &open, &ispre, s, bb->styles, bb->styles_count ); // todo: styles
|
||||||
|
assert(end > s);
|
||||||
|
if (open) {
|
||||||
|
if (!ispre) {
|
||||||
|
// open tag
|
||||||
|
*cur_attr = bbcode_open( bb, sbuf_len(out), &tag, *cur_attr );
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// scan pre to end tag
|
||||||
|
attr_t attr = attr_update_with(*cur_attr, tag.attr);
|
||||||
|
char pre[132];
|
||||||
|
if (snprintf(pre, 132, "[/%s]", idbuf) < ssizeof(pre)) {
|
||||||
|
const char* etag = strstr(end,pre);
|
||||||
|
if (etag == NULL) {
|
||||||
|
const ssize_t len = ic_strlen(end);
|
||||||
|
attrbuf_append_n(out, attr_out, end, len, attr);
|
||||||
|
end += len;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
attrbuf_append_n(out, attr_out, end, (etag - end), attr);
|
||||||
|
end = etag + ic_strlen(pre);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// pop the tag
|
||||||
|
tag_t prev;
|
||||||
|
if (bbcode_close( bb, nesting_base, tag.name, &prev)) {
|
||||||
|
*cur_attr = prev.attr;
|
||||||
|
if (prev.width.w > 0) {
|
||||||
|
// closed a width tag; restrict the output to width
|
||||||
|
bbcode_restrict_width( prev.pos, prev.width, out, attr_out);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return (end - s);
|
||||||
|
}
|
||||||
|
|
||||||
|
ic_private void bbcode_append( bbcode_t* bb, const char* s, stringbuf_t* out, attrbuf_t* attr_out ) {
|
||||||
|
if (bb == NULL || s == NULL) return;
|
||||||
|
attr_t attr = attr_none();
|
||||||
|
const ssize_t base = bb->tags_nesting; // base; will not be popped
|
||||||
|
ssize_t i = 0;
|
||||||
|
while( s[i] != 0 ) {
|
||||||
|
// handle no tags in bulk
|
||||||
|
ssize_t nobb = 0;
|
||||||
|
char c;
|
||||||
|
while( (c = s[i+nobb]) != 0) {
|
||||||
|
if (c == '[' || c == '\\') { break; }
|
||||||
|
if (c == '\x1B' && s[i+nobb+1] == '[') {
|
||||||
|
nobb++; // don't count 'ESC[' as a tag opener
|
||||||
|
}
|
||||||
|
nobb++;
|
||||||
|
}
|
||||||
|
if (nobb > 0) { attrbuf_append_n(out, attr_out, s+i, nobb, attr); }
|
||||||
|
i += nobb;
|
||||||
|
// tag
|
||||||
|
if (s[i] == '[') {
|
||||||
|
i += bbcode_process_tag(bb, s+i, base, out, attr_out, &attr);
|
||||||
|
}
|
||||||
|
else if (s[i] == '\\') {
|
||||||
|
if (s[i+1] == '\\' || s[i+1] == '[') {
|
||||||
|
attrbuf_append_n(out, attr_out, s+i+1, 1, attr); // escape '\[' and '\\'
|
||||||
|
i += 2;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
attrbuf_append_n(out, attr_out, s+i, 1, attr); // pass '\\' as is
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// pop unclosed openings
|
||||||
|
assert(bb->tags_nesting >= base);
|
||||||
|
while( bb->tags_nesting > base ) {
|
||||||
|
bbcode_tag_pop(bb,NULL);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
ic_private void bbcode_print( bbcode_t* bb, const char* s ) {
|
||||||
|
if (bb->out == NULL || bb->out_attrs == NULL || s == NULL) return;
|
||||||
|
assert(sbuf_len(bb->out) == 0 && attrbuf_len(bb->out_attrs) == 0);
|
||||||
|
bbcode_append( bb, s, bb->out, bb->out_attrs );
|
||||||
|
term_write_formatted( bb->term, sbuf_string(bb->out), attrbuf_attrs(bb->out_attrs,sbuf_len(bb->out)) );
|
||||||
|
attrbuf_clear(bb->out_attrs);
|
||||||
|
sbuf_clear(bb->out);
|
||||||
|
}
|
||||||
|
|
||||||
|
ic_private void bbcode_println( bbcode_t* bb, const char* s ) {
|
||||||
|
bbcode_print(bb,s);
|
||||||
|
term_writeln(bb->term, "");
|
||||||
|
}
|
||||||
|
|
||||||
|
ic_private void bbcode_vprintf( bbcode_t* bb, const char* fmt, va_list args ) {
|
||||||
|
if (bb->vout == NULL || fmt == NULL) return;
|
||||||
|
assert(sbuf_len(bb->vout) == 0);
|
||||||
|
sbuf_append_vprintf(bb->vout,fmt,args);
|
||||||
|
bbcode_print(bb, sbuf_string(bb->vout));
|
||||||
|
sbuf_clear(bb->vout);
|
||||||
|
}
|
||||||
|
|
||||||
|
ic_private void bbcode_printf( bbcode_t* bb, const char* fmt, ... ) {
|
||||||
|
va_list args;
|
||||||
|
va_start(args,fmt);
|
||||||
|
bbcode_vprintf(bb,fmt,args);
|
||||||
|
va_end(args);
|
||||||
|
}
|
||||||
|
|
||||||
|
ic_private ssize_t bbcode_column_width( bbcode_t* bb, const char* s ) {
|
||||||
|
if (s==NULL || s[0] == 0) return 0;
|
||||||
|
if (bb->vout == NULL) { return str_column_width(s); }
|
||||||
|
assert(sbuf_len(bb->vout) == 0);
|
||||||
|
bbcode_append( bb, s, bb->vout, NULL);
|
||||||
|
const ssize_t w = str_column_width(sbuf_string(bb->vout));
|
||||||
|
sbuf_clear(bb->vout);
|
||||||
|
return w;
|
||||||
|
}
|
37
deps/isocline/src/bbcode.h
vendored
Normal file
37
deps/isocline/src/bbcode.h
vendored
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
/* ----------------------------------------------------------------------------
|
||||||
|
Copyright (c) 2021, Daan Leijen
|
||||||
|
This is free software; you can redistribute it and/or modify it
|
||||||
|
under the terms of the MIT License. A copy of the license can be
|
||||||
|
found in the "LICENSE" file at the root of this distribution.
|
||||||
|
-----------------------------------------------------------------------------*/
|
||||||
|
#pragma once
|
||||||
|
#ifndef IC_BBCODE_H
|
||||||
|
#define IC_BBCODE_H
|
||||||
|
|
||||||
|
#include <stdarg.h>
|
||||||
|
#include "common.h"
|
||||||
|
#include "term.h"
|
||||||
|
|
||||||
|
struct bbcode_s;
|
||||||
|
typedef struct bbcode_s bbcode_t;
|
||||||
|
|
||||||
|
ic_private bbcode_t* bbcode_new( alloc_t* mem, term_t* term );
|
||||||
|
ic_private void bbcode_free( bbcode_t* bb );
|
||||||
|
|
||||||
|
ic_private void bbcode_style_add( bbcode_t* bb, const char* style_name, attr_t attr );
|
||||||
|
ic_private void bbcode_style_def( bbcode_t* bb, const char* style_name, const char* s );
|
||||||
|
ic_private void bbcode_style_open( bbcode_t* bb, const char* fmt );
|
||||||
|
ic_private void bbcode_style_close( bbcode_t* bb, const char* fmt );
|
||||||
|
ic_private attr_t bbcode_style( bbcode_t* bb, const char* style_name );
|
||||||
|
|
||||||
|
ic_private void bbcode_print( bbcode_t* bb, const char* s );
|
||||||
|
ic_private void bbcode_println( bbcode_t* bb, const char* s );
|
||||||
|
ic_private void bbcode_printf( bbcode_t* bb, const char* fmt, ... );
|
||||||
|
ic_private void bbcode_vprintf( bbcode_t* bb, const char* fmt, va_list args );
|
||||||
|
|
||||||
|
ic_private ssize_t bbcode_column_width( bbcode_t* bb, const char* s );
|
||||||
|
|
||||||
|
// allows `attr_out == NULL`.
|
||||||
|
ic_private void bbcode_append( bbcode_t* bb, const char* s, stringbuf_t* out, attrbuf_t* attr_out );
|
||||||
|
|
||||||
|
#endif // IC_BBCODE_H
|
194
deps/isocline/src/bbcode_colors.c
vendored
Normal file
194
deps/isocline/src/bbcode_colors.c
vendored
Normal file
@ -0,0 +1,194 @@
|
|||||||
|
/* ----------------------------------------------------------------------------
|
||||||
|
Copyright (c) 2021, Daan Leijen
|
||||||
|
This is free software; you can redistribute it and/or modify it
|
||||||
|
under the terms of the MIT License. A copy of the license can be
|
||||||
|
found in the "LICENSE" file at the root of this distribution.
|
||||||
|
-----------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
// This file is included from "bbcode.c" and contains html color names
|
||||||
|
|
||||||
|
#include "common.h"
|
||||||
|
|
||||||
|
typedef struct style_color_s {
|
||||||
|
const char* name;
|
||||||
|
ic_color_t color;
|
||||||
|
} style_color_t;
|
||||||
|
|
||||||
|
#define IC_HTML_COLOR_COUNT (172)
|
||||||
|
|
||||||
|
// ordered list of HTML color names (so we can use binary search)
|
||||||
|
static style_color_t html_colors[IC_HTML_COLOR_COUNT+1] = {
|
||||||
|
{ "aliceblue", IC_RGB(0xf0f8ff) },
|
||||||
|
{ "ansi-aqua", IC_ANSI_AQUA },
|
||||||
|
{ "ansi-black", IC_ANSI_BLACK },
|
||||||
|
{ "ansi-blue", IC_ANSI_BLUE },
|
||||||
|
{ "ansi-cyan", IC_ANSI_CYAN },
|
||||||
|
{ "ansi-darkgray", IC_ANSI_DARKGRAY },
|
||||||
|
{ "ansi-darkgrey", IC_ANSI_DARKGRAY },
|
||||||
|
{ "ansi-default", IC_ANSI_DEFAULT },
|
||||||
|
{ "ansi-fuchsia", IC_ANSI_FUCHSIA },
|
||||||
|
{ "ansi-gray", IC_ANSI_GRAY },
|
||||||
|
{ "ansi-green", IC_ANSI_GREEN },
|
||||||
|
{ "ansi-grey", IC_ANSI_GRAY },
|
||||||
|
{ "ansi-lightgray", IC_ANSI_LIGHTGRAY },
|
||||||
|
{ "ansi-lightgrey", IC_ANSI_LIGHTGRAY },
|
||||||
|
{ "ansi-lime" , IC_ANSI_LIME },
|
||||||
|
{ "ansi-magenta", IC_ANSI_MAGENTA },
|
||||||
|
{ "ansi-maroon", IC_ANSI_MAROON },
|
||||||
|
{ "ansi-navy", IC_ANSI_NAVY },
|
||||||
|
{ "ansi-olive", IC_ANSI_OLIVE },
|
||||||
|
{ "ansi-purple", IC_ANSI_PURPLE },
|
||||||
|
{ "ansi-red", IC_ANSI_RED },
|
||||||
|
{ "ansi-silver", IC_ANSI_SILVER },
|
||||||
|
{ "ansi-teal", IC_ANSI_TEAL },
|
||||||
|
{ "ansi-white", IC_ANSI_WHITE },
|
||||||
|
{ "ansi-yellow", IC_ANSI_YELLOW },
|
||||||
|
{ "antiquewhite", IC_RGB(0xfaebd7) },
|
||||||
|
{ "aqua", IC_RGB(0x00ffff) },
|
||||||
|
{ "aquamarine", IC_RGB(0x7fffd4) },
|
||||||
|
{ "azure", IC_RGB(0xf0ffff) },
|
||||||
|
{ "beige", IC_RGB(0xf5f5dc) },
|
||||||
|
{ "bisque", IC_RGB(0xffe4c4) },
|
||||||
|
{ "black", IC_RGB(0x000000) },
|
||||||
|
{ "blanchedalmond", IC_RGB(0xffebcd) },
|
||||||
|
{ "blue", IC_RGB(0x0000ff) },
|
||||||
|
{ "blueviolet", IC_RGB(0x8a2be2) },
|
||||||
|
{ "brown", IC_RGB(0xa52a2a) },
|
||||||
|
{ "burlywood", IC_RGB(0xdeb887) },
|
||||||
|
{ "cadetblue", IC_RGB(0x5f9ea0) },
|
||||||
|
{ "chartreuse", IC_RGB(0x7fff00) },
|
||||||
|
{ "chocolate", IC_RGB(0xd2691e) },
|
||||||
|
{ "coral", IC_RGB(0xff7f50) },
|
||||||
|
{ "cornflowerblue", IC_RGB(0x6495ed) },
|
||||||
|
{ "cornsilk", IC_RGB(0xfff8dc) },
|
||||||
|
{ "crimson", IC_RGB(0xdc143c) },
|
||||||
|
{ "cyan", IC_RGB(0x00ffff) },
|
||||||
|
{ "darkblue", IC_RGB(0x00008b) },
|
||||||
|
{ "darkcyan", IC_RGB(0x008b8b) },
|
||||||
|
{ "darkgoldenrod", IC_RGB(0xb8860b) },
|
||||||
|
{ "darkgray", IC_RGB(0xa9a9a9) },
|
||||||
|
{ "darkgreen", IC_RGB(0x006400) },
|
||||||
|
{ "darkgrey", IC_RGB(0xa9a9a9) },
|
||||||
|
{ "darkkhaki", IC_RGB(0xbdb76b) },
|
||||||
|
{ "darkmagenta", IC_RGB(0x8b008b) },
|
||||||
|
{ "darkolivegreen", IC_RGB(0x556b2f) },
|
||||||
|
{ "darkorange", IC_RGB(0xff8c00) },
|
||||||
|
{ "darkorchid", IC_RGB(0x9932cc) },
|
||||||
|
{ "darkred", IC_RGB(0x8b0000) },
|
||||||
|
{ "darksalmon", IC_RGB(0xe9967a) },
|
||||||
|
{ "darkseagreen", IC_RGB(0x8fbc8f) },
|
||||||
|
{ "darkslateblue", IC_RGB(0x483d8b) },
|
||||||
|
{ "darkslategray", IC_RGB(0x2f4f4f) },
|
||||||
|
{ "darkslategrey", IC_RGB(0x2f4f4f) },
|
||||||
|
{ "darkturquoise", IC_RGB(0x00ced1) },
|
||||||
|
{ "darkviolet", IC_RGB(0x9400d3) },
|
||||||
|
{ "deeppink", IC_RGB(0xff1493) },
|
||||||
|
{ "deepskyblue", IC_RGB(0x00bfff) },
|
||||||
|
{ "dimgray", IC_RGB(0x696969) },
|
||||||
|
{ "dimgrey", IC_RGB(0x696969) },
|
||||||
|
{ "dodgerblue", IC_RGB(0x1e90ff) },
|
||||||
|
{ "firebrick", IC_RGB(0xb22222) },
|
||||||
|
{ "floralwhite", IC_RGB(0xfffaf0) },
|
||||||
|
{ "forestgreen", IC_RGB(0x228b22) },
|
||||||
|
{ "fuchsia", IC_RGB(0xff00ff) },
|
||||||
|
{ "gainsboro", IC_RGB(0xdcdcdc) },
|
||||||
|
{ "ghostwhite", IC_RGB(0xf8f8ff) },
|
||||||
|
{ "gold", IC_RGB(0xffd700) },
|
||||||
|
{ "goldenrod", IC_RGB(0xdaa520) },
|
||||||
|
{ "gray", IC_RGB(0x808080) },
|
||||||
|
{ "green", IC_RGB(0x008000) },
|
||||||
|
{ "greenyellow", IC_RGB(0xadff2f) },
|
||||||
|
{ "grey", IC_RGB(0x808080) },
|
||||||
|
{ "honeydew", IC_RGB(0xf0fff0) },
|
||||||
|
{ "hotpink", IC_RGB(0xff69b4) },
|
||||||
|
{ "indianred", IC_RGB(0xcd5c5c) },
|
||||||
|
{ "indigo", IC_RGB(0x4b0082) },
|
||||||
|
{ "ivory", IC_RGB(0xfffff0) },
|
||||||
|
{ "khaki", IC_RGB(0xf0e68c) },
|
||||||
|
{ "lavender", IC_RGB(0xe6e6fa) },
|
||||||
|
{ "lavenderblush", IC_RGB(0xfff0f5) },
|
||||||
|
{ "lawngreen", IC_RGB(0x7cfc00) },
|
||||||
|
{ "lemonchiffon", IC_RGB(0xfffacd) },
|
||||||
|
{ "lightblue", IC_RGB(0xadd8e6) },
|
||||||
|
{ "lightcoral", IC_RGB(0xf08080) },
|
||||||
|
{ "lightcyan", IC_RGB(0xe0ffff) },
|
||||||
|
{ "lightgoldenrodyellow", IC_RGB(0xfafad2) },
|
||||||
|
{ "lightgray", IC_RGB(0xd3d3d3) },
|
||||||
|
{ "lightgreen", IC_RGB(0x90ee90) },
|
||||||
|
{ "lightgrey", IC_RGB(0xd3d3d3) },
|
||||||
|
{ "lightpink", IC_RGB(0xffb6c1) },
|
||||||
|
{ "lightsalmon", IC_RGB(0xffa07a) },
|
||||||
|
{ "lightseagreen", IC_RGB(0x20b2aa) },
|
||||||
|
{ "lightskyblue", IC_RGB(0x87cefa) },
|
||||||
|
{ "lightslategray", IC_RGB(0x778899) },
|
||||||
|
{ "lightslategrey", IC_RGB(0x778899) },
|
||||||
|
{ "lightsteelblue", IC_RGB(0xb0c4de) },
|
||||||
|
{ "lightyellow", IC_RGB(0xffffe0) },
|
||||||
|
{ "lime", IC_RGB(0x00ff00) },
|
||||||
|
{ "limegreen", IC_RGB(0x32cd32) },
|
||||||
|
{ "linen", IC_RGB(0xfaf0e6) },
|
||||||
|
{ "magenta", IC_RGB(0xff00ff) },
|
||||||
|
{ "maroon", IC_RGB(0x800000) },
|
||||||
|
{ "mediumaquamarine", IC_RGB(0x66cdaa) },
|
||||||
|
{ "mediumblue", IC_RGB(0x0000cd) },
|
||||||
|
{ "mediumorchid", IC_RGB(0xba55d3) },
|
||||||
|
{ "mediumpurple", IC_RGB(0x9370db) },
|
||||||
|
{ "mediumseagreen", IC_RGB(0x3cb371) },
|
||||||
|
{ "mediumslateblue", IC_RGB(0x7b68ee) },
|
||||||
|
{ "mediumspringgreen", IC_RGB(0x00fa9a) },
|
||||||
|
{ "mediumturquoise", IC_RGB(0x48d1cc) },
|
||||||
|
{ "mediumvioletred", IC_RGB(0xc71585) },
|
||||||
|
{ "midnightblue", IC_RGB(0x191970) },
|
||||||
|
{ "mintcream", IC_RGB(0xf5fffa) },
|
||||||
|
{ "mistyrose", IC_RGB(0xffe4e1) },
|
||||||
|
{ "moccasin", IC_RGB(0xffe4b5) },
|
||||||
|
{ "navajowhite", IC_RGB(0xffdead) },
|
||||||
|
{ "navy", IC_RGB(0x000080) },
|
||||||
|
{ "oldlace", IC_RGB(0xfdf5e6) },
|
||||||
|
{ "olive", IC_RGB(0x808000) },
|
||||||
|
{ "olivedrab", IC_RGB(0x6b8e23) },
|
||||||
|
{ "orange", IC_RGB(0xffa500) },
|
||||||
|
{ "orangered", IC_RGB(0xff4500) },
|
||||||
|
{ "orchid", IC_RGB(0xda70d6) },
|
||||||
|
{ "palegoldenrod", IC_RGB(0xeee8aa) },
|
||||||
|
{ "palegreen", IC_RGB(0x98fb98) },
|
||||||
|
{ "paleturquoise", IC_RGB(0xafeeee) },
|
||||||
|
{ "palevioletred", IC_RGB(0xdb7093) },
|
||||||
|
{ "papayawhip", IC_RGB(0xffefd5) },
|
||||||
|
{ "peachpuff", IC_RGB(0xffdab9) },
|
||||||
|
{ "peru", IC_RGB(0xcd853f) },
|
||||||
|
{ "pink", IC_RGB(0xffc0cb) },
|
||||||
|
{ "plum", IC_RGB(0xdda0dd) },
|
||||||
|
{ "powderblue", IC_RGB(0xb0e0e6) },
|
||||||
|
{ "purple", IC_RGB(0x800080) },
|
||||||
|
{ "rebeccapurple", IC_RGB(0x663399) },
|
||||||
|
{ "red", IC_RGB(0xff0000) },
|
||||||
|
{ "rosybrown", IC_RGB(0xbc8f8f) },
|
||||||
|
{ "royalblue", IC_RGB(0x4169e1) },
|
||||||
|
{ "saddlebrown", IC_RGB(0x8b4513) },
|
||||||
|
{ "salmon", IC_RGB(0xfa8072) },
|
||||||
|
{ "sandybrown", IC_RGB(0xf4a460) },
|
||||||
|
{ "seagreen", IC_RGB(0x2e8b57) },
|
||||||
|
{ "seashell", IC_RGB(0xfff5ee) },
|
||||||
|
{ "sienna", IC_RGB(0xa0522d) },
|
||||||
|
{ "silver", IC_RGB(0xc0c0c0) },
|
||||||
|
{ "skyblue", IC_RGB(0x87ceeb) },
|
||||||
|
{ "slateblue", IC_RGB(0x6a5acd) },
|
||||||
|
{ "slategray", IC_RGB(0x708090) },
|
||||||
|
{ "slategrey", IC_RGB(0x708090) },
|
||||||
|
{ "snow", IC_RGB(0xfffafa) },
|
||||||
|
{ "springgreen", IC_RGB(0x00ff7f) },
|
||||||
|
{ "steelblue", IC_RGB(0x4682b4) },
|
||||||
|
{ "tan", IC_RGB(0xd2b48c) },
|
||||||
|
{ "teal", IC_RGB(0x008080) },
|
||||||
|
{ "thistle", IC_RGB(0xd8bfd8) },
|
||||||
|
{ "tomato", IC_RGB(0xff6347) },
|
||||||
|
{ "turquoise", IC_RGB(0x40e0d0) },
|
||||||
|
{ "violet", IC_RGB(0xee82ee) },
|
||||||
|
{ "wheat", IC_RGB(0xf5deb3) },
|
||||||
|
{ "white", IC_RGB(0xffffff) },
|
||||||
|
{ "whitesmoke", IC_RGB(0xf5f5f5) },
|
||||||
|
{ "yellow", IC_RGB(0xffff00) },
|
||||||
|
{ "yellowgreen", IC_RGB(0x9acd32) },
|
||||||
|
{NULL, 0}
|
||||||
|
};
|
347
deps/isocline/src/common.c
vendored
Normal file
347
deps/isocline/src/common.c
vendored
Normal file
@ -0,0 +1,347 @@
|
|||||||
|
/* ----------------------------------------------------------------------------
|
||||||
|
Copyright (c) 2021, Daan Leijen
|
||||||
|
This is free software; you can redistribute it and/or modify it
|
||||||
|
under the terms of the MIT License. A copy of the license can be
|
||||||
|
found in the "LICENSE" file at the root of this distribution.
|
||||||
|
-----------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
#include <string.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdarg.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include "common.h"
|
||||||
|
|
||||||
|
|
||||||
|
//-------------------------------------------------------------
|
||||||
|
// String wrappers for ssize_t
|
||||||
|
//-------------------------------------------------------------
|
||||||
|
|
||||||
|
ic_private ssize_t ic_strlen( const char* s ) {
|
||||||
|
if (s==NULL) return 0;
|
||||||
|
return to_ssize_t(strlen(s));
|
||||||
|
}
|
||||||
|
|
||||||
|
ic_private void ic_memmove( void* dest, const void* src, ssize_t n ) {
|
||||||
|
assert(dest!=NULL && src != NULL);
|
||||||
|
if (n <= 0) return;
|
||||||
|
memmove(dest,src,to_size_t(n));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
ic_private void ic_memcpy( void* dest, const void* src, ssize_t n ) {
|
||||||
|
assert(dest!=NULL && src != NULL);
|
||||||
|
if (dest == NULL || src == NULL || n <= 0) return;
|
||||||
|
memcpy(dest,src,to_size_t(n));
|
||||||
|
}
|
||||||
|
|
||||||
|
ic_private void ic_memset(void* dest, uint8_t value, ssize_t n) {
|
||||||
|
assert(dest!=NULL);
|
||||||
|
if (dest == NULL || n <= 0) return;
|
||||||
|
memset(dest,(int8_t)value,to_size_t(n));
|
||||||
|
}
|
||||||
|
|
||||||
|
ic_private bool ic_memnmove( void* dest, ssize_t dest_size, const void* src, ssize_t n ) {
|
||||||
|
assert(dest!=NULL && src != NULL);
|
||||||
|
if (n <= 0) return true;
|
||||||
|
if (dest_size < n) { assert(false); return false; }
|
||||||
|
memmove(dest,src,to_size_t(n));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
ic_private bool ic_strcpy( char* dest, ssize_t dest_size /* including 0 */, const char* src) {
|
||||||
|
assert(dest!=NULL && src != NULL);
|
||||||
|
if (dest == NULL || dest_size <= 0) return false;
|
||||||
|
ssize_t slen = ic_strlen(src);
|
||||||
|
if (slen >= dest_size) return false;
|
||||||
|
strcpy(dest,src);
|
||||||
|
assert(dest[slen] == 0);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
ic_private bool ic_strncpy( char* dest, ssize_t dest_size /* including 0 */, const char* src, ssize_t n) {
|
||||||
|
assert(dest!=NULL && n < dest_size);
|
||||||
|
if (dest == NULL || dest_size <= 0) return false;
|
||||||
|
if (n >= dest_size) return false;
|
||||||
|
if (src==NULL || n <= 0) {
|
||||||
|
dest[0] = 0;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
strncpy(dest,src,to_size_t(n));
|
||||||
|
dest[n] = 0;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
//-------------------------------------------------------------
|
||||||
|
// String matching
|
||||||
|
//-------------------------------------------------------------
|
||||||
|
|
||||||
|
ic_public bool ic_starts_with( const char* s, const char* prefix ) {
|
||||||
|
if (s==prefix) return true;
|
||||||
|
if (prefix==NULL) return true;
|
||||||
|
if (s==NULL) return false;
|
||||||
|
|
||||||
|
ssize_t i;
|
||||||
|
for( i = 0; s[i] != 0 && prefix[i] != 0; i++) {
|
||||||
|
if (s[i] != prefix[i]) return false;
|
||||||
|
}
|
||||||
|
return (prefix[i] == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
ic_private char ic_tolower( char c ) {
|
||||||
|
return (c >= 'A' && c <= 'Z' ? c - 'A' + 'a' : c);
|
||||||
|
}
|
||||||
|
|
||||||
|
ic_private void ic_str_tolower(char* s) {
|
||||||
|
while(*s != 0) {
|
||||||
|
*s = ic_tolower(*s);
|
||||||
|
s++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ic_public bool ic_istarts_with( const char* s, const char* prefix ) {
|
||||||
|
if (s==prefix) return true;
|
||||||
|
if (prefix==NULL) return true;
|
||||||
|
if (s==NULL) return false;
|
||||||
|
|
||||||
|
ssize_t i;
|
||||||
|
for( i = 0; s[i] != 0 && prefix[i] != 0; i++) {
|
||||||
|
if (ic_tolower(s[i]) != ic_tolower(prefix[i])) return false;
|
||||||
|
}
|
||||||
|
return (prefix[i] == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
ic_private int ic_strnicmp(const char* s1, const char* s2, ssize_t n) {
|
||||||
|
if (s1 == NULL && s2 == NULL) return 0;
|
||||||
|
if (s1 == NULL) return -1;
|
||||||
|
if (s2 == NULL) return 1;
|
||||||
|
ssize_t i;
|
||||||
|
for (i = 0; s1[i] != 0 && i < n; i++) { // note: if s2[i] == 0 the loop will stop as c1 != c2
|
||||||
|
char c1 = ic_tolower(s1[i]);
|
||||||
|
char c2 = ic_tolower(s2[i]);
|
||||||
|
if (c1 < c2) return -1;
|
||||||
|
if (c1 > c2) return 1;
|
||||||
|
}
|
||||||
|
return ((i >= n || s2[i] == 0) ? 0 : -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
ic_private int ic_stricmp(const char* s1, const char* s2) {
|
||||||
|
ssize_t len1 = ic_strlen(s1);
|
||||||
|
ssize_t len2 = ic_strlen(s2);
|
||||||
|
if (len1 < len2) return -1;
|
||||||
|
if (len1 > len2) return 1;
|
||||||
|
return (ic_strnicmp(s1, s2, (len1 >= len2 ? len1 : len2)));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static const char* ic_stristr(const char* s, const char* pat) {
|
||||||
|
if (s==NULL) return NULL;
|
||||||
|
if (pat==NULL || pat[0] == 0) return s;
|
||||||
|
ssize_t patlen = ic_strlen(pat);
|
||||||
|
for (ssize_t i = 0; s[i] != 0; i++) {
|
||||||
|
if (ic_strnicmp(s + i, pat, patlen) == 0) return (s+i);
|
||||||
|
}
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
ic_private bool ic_contains(const char* big, const char* s) {
|
||||||
|
if (big == NULL) return false;
|
||||||
|
if (s == NULL) return true;
|
||||||
|
return (strstr(big,s) != NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
ic_private bool ic_icontains(const char* big, const char* s) {
|
||||||
|
if (big == NULL) return false;
|
||||||
|
if (s == NULL) return true;
|
||||||
|
return (ic_stristr(big,s) != NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//-------------------------------------------------------------
|
||||||
|
// Unicode
|
||||||
|
// QUTF-8: See <https://github.com/koka-lang/koka/blob/master/kklib/include/kklib/string.h>
|
||||||
|
// Raw bytes are code points 0xEE000 - 0xEE0FF
|
||||||
|
//-------------------------------------------------------------
|
||||||
|
#define IC_UNICODE_RAW ((unicode_t)(0xEE000U))
|
||||||
|
|
||||||
|
ic_private unicode_t unicode_from_raw(uint8_t c) {
|
||||||
|
return (IC_UNICODE_RAW + c);
|
||||||
|
}
|
||||||
|
|
||||||
|
ic_private bool unicode_is_raw(unicode_t u, uint8_t* c) {
|
||||||
|
if (u >= IC_UNICODE_RAW && u <= IC_UNICODE_RAW + 0xFF) {
|
||||||
|
*c = (uint8_t)(u - IC_UNICODE_RAW);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ic_private void unicode_to_qutf8(unicode_t u, uint8_t buf[5]) {
|
||||||
|
memset(buf, 0, 5);
|
||||||
|
if (u <= 0x7F) {
|
||||||
|
buf[0] = (uint8_t)u;
|
||||||
|
}
|
||||||
|
else if (u <= 0x07FF) {
|
||||||
|
buf[0] = (0xC0 | ((uint8_t)(u >> 6)));
|
||||||
|
buf[1] = (0x80 | (((uint8_t)u) & 0x3F));
|
||||||
|
}
|
||||||
|
else if (u <= 0xFFFF) {
|
||||||
|
buf[0] = (0xE0 | ((uint8_t)(u >> 12)));
|
||||||
|
buf[1] = (0x80 | (((uint8_t)(u >> 6)) & 0x3F));
|
||||||
|
buf[2] = (0x80 | (((uint8_t)u) & 0x3F));
|
||||||
|
}
|
||||||
|
else if (u <= 0x10FFFF) {
|
||||||
|
if (unicode_is_raw(u, &buf[0])) {
|
||||||
|
buf[1] = 0;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
buf[0] = (0xF0 | ((uint8_t)(u >> 18)));
|
||||||
|
buf[1] = (0x80 | (((uint8_t)(u >> 12)) & 0x3F));
|
||||||
|
buf[2] = (0x80 | (((uint8_t)(u >> 6)) & 0x3F));
|
||||||
|
buf[3] = (0x80 | (((uint8_t)u) & 0x3F));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// is this a utf8 continuation byte?
|
||||||
|
ic_private bool utf8_is_cont(uint8_t c) {
|
||||||
|
return ((c & 0xC0) == 0x80);
|
||||||
|
}
|
||||||
|
|
||||||
|
ic_private unicode_t unicode_from_qutf8(const uint8_t* s, ssize_t len, ssize_t* count) {
|
||||||
|
unicode_t c0 = 0;
|
||||||
|
if (len <= 0 || s == NULL) {
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
// 1 byte
|
||||||
|
c0 = s[0];
|
||||||
|
if (c0 <= 0x7F && len >= 1) {
|
||||||
|
if (count != NULL) *count = 1;
|
||||||
|
return c0;
|
||||||
|
}
|
||||||
|
else if (c0 <= 0xC1) { // invalid continuation byte or invalid 0xC0, 0xC1
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
// 2 bytes
|
||||||
|
else if (c0 <= 0xDF && len >= 2 && utf8_is_cont(s[1])) {
|
||||||
|
if (count != NULL) *count = 2;
|
||||||
|
return (((c0 & 0x1F) << 6) | (s[1] & 0x3F));
|
||||||
|
}
|
||||||
|
// 3 bytes: reject overlong and surrogate halves
|
||||||
|
else if (len >= 3 &&
|
||||||
|
((c0 == 0xE0 && s[1] >= 0xA0 && s[1] <= 0xBF && utf8_is_cont(s[2])) ||
|
||||||
|
(c0 >= 0xE1 && c0 <= 0xEC && utf8_is_cont(s[1]) && utf8_is_cont(s[2]))
|
||||||
|
))
|
||||||
|
{
|
||||||
|
if (count != NULL) *count = 3;
|
||||||
|
return (((c0 & 0x0F) << 12) | ((unicode_t)(s[1] & 0x3F) << 6) | (s[2] & 0x3F));
|
||||||
|
}
|
||||||
|
// 4 bytes: reject overlong
|
||||||
|
else if (len >= 4 &&
|
||||||
|
(((c0 == 0xF0 && s[1] >= 0x90 && s[1] <= 0xBF && utf8_is_cont(s[2]) && utf8_is_cont(s[3])) ||
|
||||||
|
(c0 >= 0xF1 && c0 <= 0xF3 && utf8_is_cont(s[1]) && utf8_is_cont(s[2]) && utf8_is_cont(s[3])) ||
|
||||||
|
(c0 == 0xF4 && s[1] >= 0x80 && s[1] <= 0x8F && utf8_is_cont(s[2]) && utf8_is_cont(s[3])))
|
||||||
|
))
|
||||||
|
{
|
||||||
|
if (count != NULL) *count = 4;
|
||||||
|
return (((c0 & 0x07) << 18) | ((unicode_t)(s[1] & 0x3F) << 12) | ((unicode_t)(s[2] & 0x3F) << 6) | (s[3] & 0x3F));
|
||||||
|
}
|
||||||
|
fail:
|
||||||
|
if (count != NULL) *count = 1;
|
||||||
|
return unicode_from_raw(s[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//-------------------------------------------------------------
|
||||||
|
// Debug
|
||||||
|
//-------------------------------------------------------------
|
||||||
|
|
||||||
|
#if defined(IC_NO_DEBUG_MSG)
|
||||||
|
// nothing
|
||||||
|
#elif !defined(IC_DEBUG_TO_FILE)
|
||||||
|
ic_private void debug_msg(const char* fmt, ...) {
|
||||||
|
if (getenv("ISOCLINE_DEBUG")) {
|
||||||
|
va_list args;
|
||||||
|
va_start(args, fmt);
|
||||||
|
vfprintf(stderr, fmt, args);
|
||||||
|
va_end(args);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
ic_private void debug_msg(const char* fmt, ...) {
|
||||||
|
static int debug_init;
|
||||||
|
static const char* debug_fname = "isocline.debug.txt";
|
||||||
|
// initialize?
|
||||||
|
if (debug_init==0) {
|
||||||
|
debug_init = -1;
|
||||||
|
const char* rdebug = getenv("ISOCLINE_DEBUG");
|
||||||
|
if (rdebug!=NULL && strcmp(rdebug,"1") == 0) {
|
||||||
|
FILE* fdbg = fopen(debug_fname, "w");
|
||||||
|
if (fdbg!=NULL) {
|
||||||
|
debug_init = 1;
|
||||||
|
fclose(fdbg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (debug_init <= 0) return;
|
||||||
|
|
||||||
|
// write debug messages
|
||||||
|
FILE* fdbg = fopen(debug_fname, "a");
|
||||||
|
if (fdbg==NULL) return;
|
||||||
|
va_list args;
|
||||||
|
va_start(args, fmt);
|
||||||
|
vfprintf(fdbg, fmt, args);
|
||||||
|
fclose(fdbg);
|
||||||
|
va_end(args);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
//-------------------------------------------------------------
|
||||||
|
// Allocation
|
||||||
|
//-------------------------------------------------------------
|
||||||
|
|
||||||
|
ic_private void* mem_malloc(alloc_t* mem, ssize_t sz) {
|
||||||
|
return mem->malloc(to_size_t(sz));
|
||||||
|
}
|
||||||
|
|
||||||
|
ic_private void* mem_zalloc(alloc_t* mem, ssize_t sz) {
|
||||||
|
void* p = mem_malloc(mem, sz);
|
||||||
|
if (p != NULL) memset(p, 0, to_size_t(sz));
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
|
||||||
|
ic_private void* mem_realloc(alloc_t* mem, void* p, ssize_t newsz) {
|
||||||
|
return mem->realloc(p, to_size_t(newsz));
|
||||||
|
}
|
||||||
|
|
||||||
|
ic_private void mem_free(alloc_t* mem, const void* p) {
|
||||||
|
mem->free((void*)p);
|
||||||
|
}
|
||||||
|
|
||||||
|
ic_private char* mem_strdup(alloc_t* mem, const char* s) {
|
||||||
|
if (s==NULL) return NULL;
|
||||||
|
ssize_t n = ic_strlen(s);
|
||||||
|
char* p = mem_malloc_tp_n(mem, char, n+1);
|
||||||
|
if (p == NULL) return NULL;
|
||||||
|
ic_memcpy(p, s, n+1);
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
|
||||||
|
ic_private char* mem_strndup(alloc_t* mem, const char* s, ssize_t n) {
|
||||||
|
if (s==NULL || n < 0) return NULL;
|
||||||
|
char* p = mem_malloc_tp_n(mem, char, n+1);
|
||||||
|
if (p == NULL) return NULL;
|
||||||
|
ssize_t i;
|
||||||
|
for (i = 0; i < n && s[i] != 0; i++) {
|
||||||
|
p[i] = s[i];
|
||||||
|
}
|
||||||
|
assert(i <= n);
|
||||||
|
p[i] = 0;
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
|
187
deps/isocline/src/common.h
vendored
Normal file
187
deps/isocline/src/common.h
vendored
Normal file
@ -0,0 +1,187 @@
|
|||||||
|
/* ----------------------------------------------------------------------------
|
||||||
|
Copyright (c) 2021, Daan Leijen
|
||||||
|
This is free software; you can redistribute it and/or modify it
|
||||||
|
under the terms of the MIT License. A copy of the license can be
|
||||||
|
found in the "LICENSE" file at the root of this distribution.
|
||||||
|
-----------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#ifndef IC_COMMON_H
|
||||||
|
#define IC_COMMON_H
|
||||||
|
|
||||||
|
//-------------------------------------------------------------
|
||||||
|
// Headers and defines
|
||||||
|
//-------------------------------------------------------------
|
||||||
|
|
||||||
|
#include <sys/types.h> // ssize_t
|
||||||
|
#include <limits.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <assert.h>
|
||||||
|
#include "../include/isocline.h" // ic_malloc_fun_t, ic_color_t etc.
|
||||||
|
|
||||||
|
# ifdef __cplusplus
|
||||||
|
# define ic_extern_c extern "C"
|
||||||
|
# else
|
||||||
|
# define ic_extern_c
|
||||||
|
# endif
|
||||||
|
|
||||||
|
#if defined(IC_SEPARATE_OBJS)
|
||||||
|
# define ic_public ic_extern_c
|
||||||
|
# if defined(__GNUC__) // includes clang and icc
|
||||||
|
# define ic_private __attribute__((visibility("hidden")))
|
||||||
|
# else
|
||||||
|
# define ic_private
|
||||||
|
# endif
|
||||||
|
#else
|
||||||
|
# define ic_private static
|
||||||
|
# define ic_public ic_extern_c
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define ic_unused(x) (void)(x)
|
||||||
|
|
||||||
|
|
||||||
|
//-------------------------------------------------------------
|
||||||
|
// ssize_t
|
||||||
|
//-------------------------------------------------------------
|
||||||
|
|
||||||
|
#if defined(_MSC_VER)
|
||||||
|
typedef intptr_t ssize_t;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define ssizeof(tp) (ssize_t)(sizeof(tp))
|
||||||
|
static inline size_t to_size_t(ssize_t sz) { return (sz >= 0 ? (size_t)sz : 0); }
|
||||||
|
static inline ssize_t to_ssize_t(size_t sz) { return (sz <= SIZE_MAX/2 ? (ssize_t)sz : 0); }
|
||||||
|
|
||||||
|
ic_private void ic_memmove(void* dest, const void* src, ssize_t n);
|
||||||
|
ic_private void ic_memcpy(void* dest, const void* src, ssize_t n);
|
||||||
|
ic_private void ic_memset(void* dest, uint8_t value, ssize_t n);
|
||||||
|
ic_private bool ic_memnmove(void* dest, ssize_t dest_size, const void* src, ssize_t n);
|
||||||
|
|
||||||
|
ic_private ssize_t ic_strlen(const char* s);
|
||||||
|
ic_private bool ic_strcpy(char* dest, ssize_t dest_size /* including 0 */, const char* src);
|
||||||
|
ic_private bool ic_strncpy(char* dest, ssize_t dest_size /* including 0 */, const char* src, ssize_t n);
|
||||||
|
|
||||||
|
ic_private bool ic_contains(const char* big, const char* s);
|
||||||
|
ic_private bool ic_icontains(const char* big, const char* s);
|
||||||
|
ic_private char ic_tolower(char c);
|
||||||
|
ic_private void ic_str_tolower(char* s);
|
||||||
|
ic_private int ic_stricmp(const char* s1, const char* s2);
|
||||||
|
ic_private int ic_strnicmp(const char* s1, const char* s2, ssize_t n);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//---------------------------------------------------------------------
|
||||||
|
// Unicode
|
||||||
|
//
|
||||||
|
// We use "qutf-8" (quite like utf-8) encoding and decoding.
|
||||||
|
// Internally we always use valid utf-8. If we encounter invalid
|
||||||
|
// utf-8 bytes (or bytes >= 0x80 from any other encoding) we encode
|
||||||
|
// these as special code points in the "raw plane" (0xEE000 - 0xEE0FF).
|
||||||
|
// When decoding we are then able to restore such raw bytes as-is.
|
||||||
|
// See <https://github.com/koka-lang/koka/blob/master/kklib/include/kklib/string.h>
|
||||||
|
//---------------------------------------------------------------------
|
||||||
|
|
||||||
|
typedef uint32_t unicode_t;
|
||||||
|
|
||||||
|
ic_private void unicode_to_qutf8(unicode_t u, uint8_t buf[5]);
|
||||||
|
ic_private unicode_t unicode_from_qutf8(const uint8_t* s, ssize_t len, ssize_t* nread); // validating
|
||||||
|
|
||||||
|
ic_private unicode_t unicode_from_raw(uint8_t c);
|
||||||
|
ic_private bool unicode_is_raw(unicode_t u, uint8_t* c);
|
||||||
|
|
||||||
|
ic_private bool utf8_is_cont(uint8_t c);
|
||||||
|
|
||||||
|
|
||||||
|
//-------------------------------------------------------------
|
||||||
|
// Colors
|
||||||
|
//-------------------------------------------------------------
|
||||||
|
|
||||||
|
// A color is either RGB or an ANSI code.
|
||||||
|
// (RGB colors have bit 24 set to distinguish them from the ANSI color palette colors.)
|
||||||
|
// (Isocline will automatically convert from RGB on terminals that do not support full colors)
|
||||||
|
typedef uint32_t ic_color_t;
|
||||||
|
|
||||||
|
// Create a color from a 24-bit color value.
|
||||||
|
ic_private ic_color_t ic_rgb(uint32_t hex);
|
||||||
|
|
||||||
|
// Create a color from a 8-bit red/green/blue components.
|
||||||
|
// The value of each component is capped between 0 and 255.
|
||||||
|
ic_private ic_color_t ic_rgbx(ssize_t r, ssize_t g, ssize_t b);
|
||||||
|
|
||||||
|
#define IC_COLOR_NONE (0)
|
||||||
|
#define IC_RGB(rgb) (0x1000000 | (uint32_t)(rgb)) // ic_rgb(rgb) // define to it can be used as a constant
|
||||||
|
|
||||||
|
// ANSI colors.
|
||||||
|
// The actual colors used is usually determined by the terminal theme
|
||||||
|
// See <https://en.wikipedia.org/wiki/ANSI_escape_code#3-bit_and_4-bit>
|
||||||
|
#define IC_ANSI_BLACK (30)
|
||||||
|
#define IC_ANSI_MAROON (31)
|
||||||
|
#define IC_ANSI_GREEN (32)
|
||||||
|
#define IC_ANSI_OLIVE (33)
|
||||||
|
#define IC_ANSI_NAVY (34)
|
||||||
|
#define IC_ANSI_PURPLE (35)
|
||||||
|
#define IC_ANSI_TEAL (36)
|
||||||
|
#define IC_ANSI_SILVER (37)
|
||||||
|
#define IC_ANSI_DEFAULT (39)
|
||||||
|
|
||||||
|
#define IC_ANSI_GRAY (90)
|
||||||
|
#define IC_ANSI_RED (91)
|
||||||
|
#define IC_ANSI_LIME (92)
|
||||||
|
#define IC_ANSI_YELLOW (93)
|
||||||
|
#define IC_ANSI_BLUE (94)
|
||||||
|
#define IC_ANSI_FUCHSIA (95)
|
||||||
|
#define IC_ANSI_AQUA (96)
|
||||||
|
#define IC_ANSI_WHITE (97)
|
||||||
|
|
||||||
|
#define IC_ANSI_DARKGRAY IC_ANSI_GRAY
|
||||||
|
#define IC_ANSI_LIGHTGRAY IC_ANSI_SILVER
|
||||||
|
#define IC_ANSI_MAGENTA IC_ANSI_FUCHSIA
|
||||||
|
#define IC_ANSI_CYAN IC_ANSI_AQUA
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//-------------------------------------------------------------
|
||||||
|
// Debug
|
||||||
|
//-------------------------------------------------------------
|
||||||
|
|
||||||
|
#if defined(IC_NO_DEBUG_MSG)
|
||||||
|
#define debug_msg(fmt,...) (void)(0)
|
||||||
|
#else
|
||||||
|
ic_private void debug_msg( const char* fmt, ... );
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
//-------------------------------------------------------------
|
||||||
|
// Abstract environment
|
||||||
|
//-------------------------------------------------------------
|
||||||
|
struct ic_env_s;
|
||||||
|
typedef struct ic_env_s ic_env_t;
|
||||||
|
|
||||||
|
|
||||||
|
//-------------------------------------------------------------
|
||||||
|
// Allocation
|
||||||
|
//-------------------------------------------------------------
|
||||||
|
|
||||||
|
typedef struct alloc_s {
|
||||||
|
ic_malloc_fun_t* malloc;
|
||||||
|
ic_realloc_fun_t* realloc;
|
||||||
|
ic_free_fun_t* free;
|
||||||
|
} alloc_t;
|
||||||
|
|
||||||
|
|
||||||
|
ic_private void* mem_malloc( alloc_t* mem, ssize_t sz );
|
||||||
|
ic_private void* mem_zalloc( alloc_t* mem, ssize_t sz );
|
||||||
|
ic_private void* mem_realloc( alloc_t* mem, void* p, ssize_t newsz );
|
||||||
|
ic_private void mem_free( alloc_t* mem, const void* p );
|
||||||
|
ic_private char* mem_strdup( alloc_t* mem, const char* s);
|
||||||
|
ic_private char* mem_strndup( alloc_t* mem, const char* s, ssize_t n);
|
||||||
|
|
||||||
|
#define mem_zalloc_tp(mem,tp) (tp*)mem_zalloc(mem,ssizeof(tp))
|
||||||
|
#define mem_malloc_tp_n(mem,tp,n) (tp*)mem_malloc(mem,(n)*ssizeof(tp))
|
||||||
|
#define mem_zalloc_tp_n(mem,tp,n) (tp*)mem_zalloc(mem,(n)*ssizeof(tp))
|
||||||
|
#define mem_realloc_tp(mem,tp,p,n) (tp*)mem_realloc(mem,p,(n)*ssizeof(tp))
|
||||||
|
|
||||||
|
|
||||||
|
#endif // IC_COMMON_H
|
675
deps/isocline/src/completers.c
vendored
Normal file
675
deps/isocline/src/completers.c
vendored
Normal file
@ -0,0 +1,675 @@
|
|||||||
|
/* ----------------------------------------------------------------------------
|
||||||
|
Copyright (c) 2021, Daan Leijen
|
||||||
|
This is free software; you can redistribute it and/or modify it
|
||||||
|
under the terms of the MIT License. A copy of the license can be
|
||||||
|
found in the "LICENSE" file at the root of this distribution.
|
||||||
|
-----------------------------------------------------------------------------*/
|
||||||
|
#include <string.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
#include "../include/isocline.h"
|
||||||
|
#include "common.h"
|
||||||
|
#include "env.h"
|
||||||
|
#include "stringbuf.h"
|
||||||
|
#include "completions.h"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//-------------------------------------------------------------
|
||||||
|
// Word completion
|
||||||
|
//-------------------------------------------------------------
|
||||||
|
|
||||||
|
// free variables for word completion
|
||||||
|
typedef struct word_closure_s {
|
||||||
|
long delete_before_adjust;
|
||||||
|
void* prev_env;
|
||||||
|
ic_completion_fun_t* prev_complete;
|
||||||
|
} word_closure_t;
|
||||||
|
|
||||||
|
|
||||||
|
// word completion callback
|
||||||
|
static bool token_add_completion_ex(ic_env_t* env, void* closure, const char* replacement, const char* display, const char* help, long delete_before, long delete_after) {
|
||||||
|
word_closure_t* wenv = (word_closure_t*)(closure);
|
||||||
|
// call the previous completer with an adjusted delete-before
|
||||||
|
return (*wenv->prev_complete)(env, wenv->prev_env, replacement, display, help, wenv->delete_before_adjust + delete_before, delete_after);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
ic_public void ic_complete_word(ic_completion_env_t* cenv, const char* prefix, ic_completer_fun_t* fun,
|
||||||
|
ic_is_char_class_fun_t* is_word_char)
|
||||||
|
{
|
||||||
|
if (is_word_char == NULL) is_word_char = &ic_char_is_nonseparator;
|
||||||
|
|
||||||
|
ssize_t len = ic_strlen(prefix);
|
||||||
|
ssize_t pos = len; // will be start of the 'word' (excluding a potential start quote)
|
||||||
|
while (pos > 0) {
|
||||||
|
// go back one code point
|
||||||
|
ssize_t ofs = str_prev_ofs(prefix, pos, NULL);
|
||||||
|
if (ofs <= 0) break;
|
||||||
|
if (!(*is_word_char)(prefix + (pos - ofs), (long)ofs)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
pos -= ofs;
|
||||||
|
}
|
||||||
|
if (pos < 0) { pos = 0; }
|
||||||
|
|
||||||
|
// stop if empty word
|
||||||
|
// if (len == pos) return;
|
||||||
|
|
||||||
|
// set up the closure
|
||||||
|
word_closure_t wenv;
|
||||||
|
wenv.delete_before_adjust = (long)(len - pos);
|
||||||
|
wenv.prev_complete = cenv->complete;
|
||||||
|
wenv.prev_env = cenv->env;
|
||||||
|
cenv->complete = &token_add_completion_ex;
|
||||||
|
cenv->closure = &wenv;
|
||||||
|
|
||||||
|
// and call the user completion routine
|
||||||
|
(*fun)(cenv, prefix + pos);
|
||||||
|
|
||||||
|
// restore the original environment
|
||||||
|
cenv->complete = wenv.prev_complete;
|
||||||
|
cenv->closure = wenv.prev_env;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//-------------------------------------------------------------
|
||||||
|
// Quoted word completion (with escape characters)
|
||||||
|
//-------------------------------------------------------------
|
||||||
|
|
||||||
|
// free variables for word completion
|
||||||
|
typedef struct qword_closure_s {
|
||||||
|
char escape_char;
|
||||||
|
char quote;
|
||||||
|
long delete_before_adjust;
|
||||||
|
stringbuf_t* sbuf;
|
||||||
|
void* prev_env;
|
||||||
|
ic_is_char_class_fun_t* is_word_char;
|
||||||
|
ic_completion_fun_t* prev_complete;
|
||||||
|
} qword_closure_t;
|
||||||
|
|
||||||
|
|
||||||
|
// word completion callback
|
||||||
|
static bool qword_add_completion_ex(ic_env_t* env, void* closure, const char* replacement, const char* display, const char* help,
|
||||||
|
long delete_before, long delete_after) {
|
||||||
|
qword_closure_t* wenv = (qword_closure_t*)(closure);
|
||||||
|
sbuf_replace( wenv->sbuf, replacement );
|
||||||
|
if (wenv->quote != 0) {
|
||||||
|
// add end quote
|
||||||
|
sbuf_append_char( wenv->sbuf, wenv->quote);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// escape non-word characters if it was not quoted
|
||||||
|
ssize_t pos = 0;
|
||||||
|
ssize_t next;
|
||||||
|
while ( (next = sbuf_next_ofs(wenv->sbuf, pos, NULL)) > 0 )
|
||||||
|
{
|
||||||
|
if (!(*wenv->is_word_char)(sbuf_string(wenv->sbuf) + pos, (long)next)) { // strchr(wenv->non_word_char, sbuf_char_at( wenv->sbuf, pos )) != NULL) {
|
||||||
|
sbuf_insert_char_at( wenv->sbuf, wenv->escape_char, pos);
|
||||||
|
pos++;
|
||||||
|
}
|
||||||
|
pos += next;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// and call the previous completion function
|
||||||
|
return (*wenv->prev_complete)( env, wenv->prev_env, sbuf_string(wenv->sbuf), display, help, wenv->delete_before_adjust + delete_before, delete_after );
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
ic_public void ic_complete_qword( ic_completion_env_t* cenv, const char* prefix, ic_completer_fun_t* fun, ic_is_char_class_fun_t* is_word_char ) {
|
||||||
|
ic_complete_qword_ex( cenv, prefix, fun, is_word_char, '\\', NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
ic_public void ic_complete_qword_ex( ic_completion_env_t* cenv, const char* prefix, ic_completer_fun_t* fun,
|
||||||
|
ic_is_char_class_fun_t* is_word_char, char escape_char, const char* quote_chars ) {
|
||||||
|
if (is_word_char == NULL) is_word_char = &ic_char_is_nonseparator ;
|
||||||
|
if (quote_chars == NULL) quote_chars = "'\"";
|
||||||
|
|
||||||
|
ssize_t len = ic_strlen(prefix);
|
||||||
|
ssize_t pos; // will be start of the 'word' (excluding a potential start quote)
|
||||||
|
char quote = 0;
|
||||||
|
ssize_t quote_len = 0;
|
||||||
|
|
||||||
|
// 1. look for a starting quote
|
||||||
|
if (quote_chars[0] != 0) {
|
||||||
|
// we go forward and count all quotes; if it is uneven, we need to complete quoted.
|
||||||
|
ssize_t qpos_open = -1;
|
||||||
|
ssize_t qpos_close = -1;
|
||||||
|
ssize_t qcount = 0;
|
||||||
|
pos = 0;
|
||||||
|
while(pos < len) {
|
||||||
|
if (prefix[pos] == escape_char && prefix[pos+1] != 0 &&
|
||||||
|
!(*is_word_char)(prefix + pos + 1, 1)) // strchr(non_word_char, prefix[pos+1]) != NULL
|
||||||
|
{
|
||||||
|
pos++; // skip escape and next char
|
||||||
|
}
|
||||||
|
else if (qcount % 2 == 0 && strchr(quote_chars, prefix[pos]) != NULL) {
|
||||||
|
// open quote
|
||||||
|
qpos_open = pos;
|
||||||
|
quote = prefix[pos];
|
||||||
|
qcount++;
|
||||||
|
}
|
||||||
|
else if (qcount % 2 == 1 && prefix[pos] == quote) {
|
||||||
|
// close quote
|
||||||
|
qpos_close = pos;
|
||||||
|
qcount++;
|
||||||
|
}
|
||||||
|
else if (!(*is_word_char)(prefix + pos, 1)) { // strchr(non_word_char, prefix[pos]) != NULL) {
|
||||||
|
qpos_close = -1;
|
||||||
|
}
|
||||||
|
ssize_t ofs = str_next_ofs( prefix, len, pos, NULL );
|
||||||
|
if (ofs <= 0) break;
|
||||||
|
pos += ofs;
|
||||||
|
}
|
||||||
|
if ((qcount % 2 == 0 && qpos_close >= 0) || // if the last quote is only followed by word chars, we still complete it
|
||||||
|
(qcount % 2 == 1)) // opening quote found
|
||||||
|
{
|
||||||
|
quote_len = (len - qpos_open - 1);
|
||||||
|
pos = qpos_open + 1; // pos points to the word start just after the quote.
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
quote = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. if we did not find a quoted word, look for non-word-chars
|
||||||
|
if (quote == 0) {
|
||||||
|
pos = len;
|
||||||
|
while(pos > 0) {
|
||||||
|
// go back one code point
|
||||||
|
ssize_t ofs = str_prev_ofs(prefix, pos, NULL );
|
||||||
|
if (ofs <= 0) break;
|
||||||
|
if (!(*is_word_char)(prefix + (pos - ofs), (long)ofs)) { // strchr(non_word_char, prefix[pos - ofs]) != NULL) {
|
||||||
|
// non word char, break if it is not escaped
|
||||||
|
if (pos <= ofs || prefix[pos - ofs - 1] != escape_char) break;
|
||||||
|
// otherwise go on
|
||||||
|
pos--; // skip escaped char
|
||||||
|
}
|
||||||
|
pos -= ofs;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// stop if empty word
|
||||||
|
// if (len == pos) return;
|
||||||
|
|
||||||
|
// allocate new unescaped word prefix
|
||||||
|
char* word = mem_strndup( cenv->env->mem, prefix + pos, (quote==0 ? len - pos : quote_len));
|
||||||
|
if (word == NULL) return;
|
||||||
|
|
||||||
|
if (quote == 0) {
|
||||||
|
// unescape prefix
|
||||||
|
ssize_t wlen = len - pos;
|
||||||
|
ssize_t wpos = 0;
|
||||||
|
while (wpos < wlen) {
|
||||||
|
ssize_t ofs = str_next_ofs(word, wlen, wpos, NULL);
|
||||||
|
if (ofs <= 0) break;
|
||||||
|
if (word[wpos] == escape_char && word[wpos+1] != 0 &&
|
||||||
|
!(*is_word_char)(word + wpos + 1, (long)ofs)) // strchr(non_word_char, word[wpos+1]) != NULL) {
|
||||||
|
{
|
||||||
|
ic_memmove(word + wpos, word + wpos + 1, wlen - wpos /* including 0 */);
|
||||||
|
}
|
||||||
|
wpos += ofs;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#ifdef _WIN32
|
||||||
|
else {
|
||||||
|
// remove inner quote: "c:\Program Files\"Win
|
||||||
|
ssize_t wlen = len - pos;
|
||||||
|
ssize_t wpos = 0;
|
||||||
|
while (wpos < wlen) {
|
||||||
|
ssize_t ofs = str_next_ofs(word, wlen, wpos, NULL);
|
||||||
|
if (ofs <= 0) break;
|
||||||
|
if (word[wpos] == escape_char && word[wpos+1] == quote) {
|
||||||
|
word[wpos+1] = escape_char;
|
||||||
|
ic_memmove(word + wpos, word + wpos + 1, wlen - wpos /* including 0 */);
|
||||||
|
}
|
||||||
|
wpos += ofs;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// set up the closure
|
||||||
|
qword_closure_t wenv;
|
||||||
|
wenv.quote = quote;
|
||||||
|
wenv.is_word_char = is_word_char;
|
||||||
|
wenv.escape_char = escape_char;
|
||||||
|
wenv.delete_before_adjust = (long)(len - pos);
|
||||||
|
wenv.prev_complete = cenv->complete;
|
||||||
|
wenv.prev_env = cenv->env;
|
||||||
|
wenv.sbuf = sbuf_new(cenv->env->mem);
|
||||||
|
if (wenv.sbuf == NULL) { mem_free(cenv->env->mem, word); return; }
|
||||||
|
cenv->complete = &qword_add_completion_ex;
|
||||||
|
cenv->closure = &wenv;
|
||||||
|
|
||||||
|
// and call the user completion routine
|
||||||
|
(*fun)( cenv, word );
|
||||||
|
|
||||||
|
// restore the original environment
|
||||||
|
cenv->complete = wenv.prev_complete;
|
||||||
|
cenv->closure = wenv.prev_env;
|
||||||
|
|
||||||
|
sbuf_free(wenv.sbuf);
|
||||||
|
mem_free(cenv->env->mem, word);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//-------------------------------------------------------------
|
||||||
|
// Complete file names
|
||||||
|
// Listing files
|
||||||
|
//-------------------------------------------------------------
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
typedef enum file_type_e {
|
||||||
|
// must follow BSD style LSCOLORS order
|
||||||
|
FT_DEFAULT = 0,
|
||||||
|
FT_DIR,
|
||||||
|
FT_SYM,
|
||||||
|
FT_SOCK,
|
||||||
|
FT_PIPE,
|
||||||
|
FT_BLOCK,
|
||||||
|
FT_CHAR,
|
||||||
|
FT_SETUID,
|
||||||
|
FT_SETGID,
|
||||||
|
FT_DIR_OW_STICKY,
|
||||||
|
FT_DIR_OW,
|
||||||
|
FT_DIR_STICKY,
|
||||||
|
FT_EXE,
|
||||||
|
FT_LAST
|
||||||
|
} file_type_t;
|
||||||
|
|
||||||
|
static int cli_color; // 1 enabled, 0 not initialized, -1 disabled
|
||||||
|
static const char* lscolors = "exfxcxdxbxegedabagacad"; // default BSD setting
|
||||||
|
static const char* ls_colors;
|
||||||
|
static const char* ls_colors_names[] = { "no=","di=","ln=","so=","pi=","bd=","cd=","su=","sg=","tw=","ow=","st=","ex=", NULL };
|
||||||
|
|
||||||
|
static bool ls_colors_init(void) {
|
||||||
|
if (cli_color != 0) return (cli_color >= 1);
|
||||||
|
// colors enabled?
|
||||||
|
const char* s = getenv("CLICOLOR");
|
||||||
|
if (s==NULL || (strcmp(s, "1")!=0 && strcmp(s, "") != 0)) {
|
||||||
|
cli_color = -1;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
cli_color = 1;
|
||||||
|
s = getenv("LS_COLORS");
|
||||||
|
if (s != NULL) { ls_colors = s; }
|
||||||
|
s = getenv("LSCOLORS");
|
||||||
|
if (s != NULL) { lscolors = s; }
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool ls_valid_esc(ssize_t c) {
|
||||||
|
return ((c==0 || c==1 || c==4 || c==7 || c==22 || c==24 || c==27) ||
|
||||||
|
(c >= 30 && c <= 37) || (c >= 40 && c <= 47) ||
|
||||||
|
(c >= 90 && c <= 97) || (c >= 100 && c <= 107));
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool ls_colors_from_key(stringbuf_t* sb, const char* key) {
|
||||||
|
// find key
|
||||||
|
ssize_t keylen = ic_strlen(key);
|
||||||
|
if (keylen <= 0) return false;
|
||||||
|
const char* p = strstr(ls_colors, key);
|
||||||
|
if (p == NULL) return false;
|
||||||
|
p += keylen;
|
||||||
|
if (key[keylen-1] != '=') {
|
||||||
|
if (*p != '=') return false;
|
||||||
|
p++;
|
||||||
|
}
|
||||||
|
ssize_t len = 0;
|
||||||
|
while (p[len] != 0 && p[len] != ':') {
|
||||||
|
len++;
|
||||||
|
}
|
||||||
|
if (len <= 0) return false;
|
||||||
|
sbuf_append(sb, "[ansi-sgr=\"" );
|
||||||
|
sbuf_append_n(sb, p, len );
|
||||||
|
sbuf_append(sb, "\"]");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int ls_colors_from_char(char c) {
|
||||||
|
if (c >= 'a' && c <= 'h') { return (c - 'a'); }
|
||||||
|
else if (c >= 'A' && c <= 'H') { return (c - 'A') + 8; }
|
||||||
|
else if (c == 'x') { return 256; }
|
||||||
|
else return 256; // default
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool ls_colors_append(stringbuf_t* sb, file_type_t ft, const char* ext) {
|
||||||
|
if (!ls_colors_init()) return false;
|
||||||
|
if (ls_colors != NULL) {
|
||||||
|
// GNU style
|
||||||
|
if (ft == FT_DEFAULT && ext != NULL) {
|
||||||
|
// first try extension match
|
||||||
|
if (ls_colors_from_key(sb, ext)) return true;
|
||||||
|
}
|
||||||
|
if (ft >= FT_DEFAULT && ft < FT_LAST) {
|
||||||
|
// then a filetype match
|
||||||
|
const char* key = ls_colors_names[ft];
|
||||||
|
if (ls_colors_from_key(sb, key)) return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (lscolors != NULL) {
|
||||||
|
// BSD style
|
||||||
|
char fg = 'x';
|
||||||
|
char bg = 'x';
|
||||||
|
if (ic_strlen(lscolors) > (2*(ssize_t)ft)+1) {
|
||||||
|
fg = lscolors[2*ft];
|
||||||
|
bg = lscolors[2*ft + 1];
|
||||||
|
}
|
||||||
|
sbuf_appendf(sb, "[ansi-color=%d ansi-bgcolor=%d]", ls_colors_from_char(fg), ls_colors_from_char(bg) );
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void ls_colorize(bool no_lscolor, stringbuf_t* sb, file_type_t ft, const char* name, const char* ext, char dirsep) {
|
||||||
|
bool close = (no_lscolor ? false : ls_colors_append( sb, ft, ext));
|
||||||
|
sbuf_append(sb, "[!pre]" );
|
||||||
|
sbuf_append(sb, name);
|
||||||
|
if (dirsep != 0) sbuf_append_char(sb, dirsep);
|
||||||
|
sbuf_append(sb,"[/pre]" );
|
||||||
|
if (close) { sbuf_append(sb, "[/]"); }
|
||||||
|
}
|
||||||
|
|
||||||
|
#if defined(_WIN32)
|
||||||
|
#include <io.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
|
||||||
|
static bool os_is_dir(const char* cpath) {
|
||||||
|
struct _stat64 st = { 0 };
|
||||||
|
_stat64(cpath, &st);
|
||||||
|
return ((st.st_mode & _S_IFDIR) != 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static file_type_t os_get_filetype(const char* cpath) {
|
||||||
|
struct _stat64 st = { 0 };
|
||||||
|
_stat64(cpath, &st);
|
||||||
|
if (((st.st_mode) & _S_IFDIR) != 0) return FT_DIR;
|
||||||
|
if (((st.st_mode) & _S_IFCHR) != 0) return FT_CHAR;
|
||||||
|
if (((st.st_mode) & _S_IFIFO) != 0) return FT_PIPE;
|
||||||
|
if (((st.st_mode) & _S_IEXEC) != 0) return FT_EXE;
|
||||||
|
return FT_DEFAULT;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#define dir_cursor intptr_t
|
||||||
|
#define dir_entry struct __finddata64_t
|
||||||
|
|
||||||
|
static bool os_findfirst(alloc_t* mem, const char* path, dir_cursor* d, dir_entry* entry) {
|
||||||
|
stringbuf_t* spath = sbuf_new(mem);
|
||||||
|
if (spath == NULL) return false;
|
||||||
|
sbuf_append(spath, path);
|
||||||
|
sbuf_append(spath, "\\*");
|
||||||
|
*d = _findfirsti64(sbuf_string(spath), entry);
|
||||||
|
mem_free(mem,spath);
|
||||||
|
return (*d != -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool os_findnext(dir_cursor d, dir_entry* entry) {
|
||||||
|
return (_findnexti64(d, entry) == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void os_findclose(dir_cursor d) {
|
||||||
|
_findclose(d);
|
||||||
|
}
|
||||||
|
|
||||||
|
static const char* os_direntry_name(dir_entry* entry) {
|
||||||
|
return entry->name;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool os_path_is_absolute( const char* path ) {
|
||||||
|
if (path != NULL && path[0] != 0 && path[1] == ':' && (path[2] == '\\' || path[2] == '/' || path[2] == 0)) {
|
||||||
|
char drive = path[0];
|
||||||
|
return ((drive >= 'A' && drive <= 'Z') || (drive >= 'a' && drive <= 'z'));
|
||||||
|
}
|
||||||
|
else return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
ic_private char ic_dirsep(void) {
|
||||||
|
return '\\';
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
|
||||||
|
#include <sys/types.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
#include <dirent.h>
|
||||||
|
#include <errno.h>
|
||||||
|
|
||||||
|
static bool os_is_dir(const char* cpath) {
|
||||||
|
struct stat st;
|
||||||
|
memset(&st, 0, sizeof(st));
|
||||||
|
stat(cpath, &st);
|
||||||
|
return (S_ISDIR(st.st_mode));
|
||||||
|
}
|
||||||
|
|
||||||
|
static file_type_t os_get_filetype(const char* cpath) {
|
||||||
|
struct stat st;
|
||||||
|
memset(&st, 0, sizeof(st));
|
||||||
|
lstat(cpath, &st);
|
||||||
|
switch ((st.st_mode)&S_IFMT) {
|
||||||
|
case S_IFSOCK: return FT_SOCK;
|
||||||
|
case S_IFLNK: {
|
||||||
|
return FT_SYM;
|
||||||
|
}
|
||||||
|
case S_IFIFO: return FT_PIPE;
|
||||||
|
case S_IFCHR: return FT_CHAR;
|
||||||
|
case S_IFBLK: return FT_BLOCK;
|
||||||
|
case S_IFDIR: {
|
||||||
|
if ((st.st_mode & S_ISUID) != 0) return FT_SETUID;
|
||||||
|
if ((st.st_mode & S_ISGID) != 0) return FT_SETGID;
|
||||||
|
if ((st.st_mode & S_IWGRP) != 0 && (st.st_mode & S_ISVTX) != 0) return FT_DIR_OW_STICKY;
|
||||||
|
if ((st.st_mode & S_IWGRP)) return FT_DIR_OW;
|
||||||
|
if ((st.st_mode & S_ISVTX)) return FT_DIR_STICKY;
|
||||||
|
return FT_DIR;
|
||||||
|
}
|
||||||
|
case S_IFREG:
|
||||||
|
default: {
|
||||||
|
if ((st.st_mode & S_IXUSR) != 0) return FT_EXE;
|
||||||
|
return FT_DEFAULT;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#define dir_cursor DIR*
|
||||||
|
#define dir_entry struct dirent*
|
||||||
|
|
||||||
|
static bool os_findnext(dir_cursor d, dir_entry* entry) {
|
||||||
|
*entry = readdir(d);
|
||||||
|
return (*entry != NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool os_findfirst(alloc_t* mem, const char* cpath, dir_cursor* d, dir_entry* entry) {
|
||||||
|
ic_unused(mem);
|
||||||
|
*d = opendir(cpath);
|
||||||
|
if (*d == NULL) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return os_findnext(*d, entry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void os_findclose(dir_cursor d) {
|
||||||
|
closedir(d);
|
||||||
|
}
|
||||||
|
|
||||||
|
static const char* os_direntry_name(dir_entry* entry) {
|
||||||
|
return (*entry)->d_name;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool os_path_is_absolute( const char* path ) {
|
||||||
|
return (path != NULL && path[0] == '/');
|
||||||
|
}
|
||||||
|
|
||||||
|
ic_private char ic_dirsep(void) {
|
||||||
|
return '/';
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//-------------------------------------------------------------
|
||||||
|
// File completion
|
||||||
|
//-------------------------------------------------------------
|
||||||
|
|
||||||
|
static bool ends_with_n(const char* name, ssize_t name_len, const char* ending, ssize_t len) {
|
||||||
|
if (name_len < len) return false;
|
||||||
|
if (ending == NULL || len <= 0) return true;
|
||||||
|
for (ssize_t i = 1; i <= len; i++) {
|
||||||
|
char c1 = name[name_len - i];
|
||||||
|
char c2 = ending[len - i];
|
||||||
|
#ifdef _WIN32
|
||||||
|
if (ic_tolower(c1) != ic_tolower(c2)) return false;
|
||||||
|
#else
|
||||||
|
if (c1 != c2) return false;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool match_extension(const char* name, const char* extensions) {
|
||||||
|
if (extensions == NULL || extensions[0] == 0) return true;
|
||||||
|
if (name == NULL) return false;
|
||||||
|
ssize_t name_len = ic_strlen(name);
|
||||||
|
ssize_t len = ic_strlen(extensions);
|
||||||
|
ssize_t cur = 0;
|
||||||
|
//debug_msg("match extensions: %s ~ %s", name, extensions);
|
||||||
|
for (ssize_t end = 0; end <= len; end++) {
|
||||||
|
if (extensions[end] == ';' || extensions[end] == 0) {
|
||||||
|
if (ends_with_n(name, name_len, extensions+cur, (end - cur))) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
cur = end+1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool filename_complete_indir( ic_completion_env_t* cenv, stringbuf_t* dir,
|
||||||
|
stringbuf_t* dir_prefix, stringbuf_t* display,
|
||||||
|
const char* base_prefix,
|
||||||
|
char dir_sep, const char* extensions )
|
||||||
|
{
|
||||||
|
dir_cursor d = 0;
|
||||||
|
dir_entry entry;
|
||||||
|
bool cont = true;
|
||||||
|
if (os_findfirst(cenv->env->mem, sbuf_string(dir), &d, &entry)) {
|
||||||
|
do {
|
||||||
|
const char* name = os_direntry_name(&entry);
|
||||||
|
if (name != NULL && strcmp(name, ".") != 0 && strcmp(name, "..") != 0 &&
|
||||||
|
ic_istarts_with(name, base_prefix))
|
||||||
|
{
|
||||||
|
// possible match, first check if it is a directory
|
||||||
|
file_type_t ft;
|
||||||
|
bool isdir;
|
||||||
|
const ssize_t plen = sbuf_len(dir_prefix);
|
||||||
|
sbuf_append(dir_prefix, name);
|
||||||
|
{ // check directory and potentially add a dirsep to the dir_prefix
|
||||||
|
const ssize_t dlen = sbuf_len(dir);
|
||||||
|
sbuf_append_char(dir,ic_dirsep());
|
||||||
|
sbuf_append(dir,name);
|
||||||
|
ft = os_get_filetype(sbuf_string(dir));
|
||||||
|
isdir = os_is_dir(sbuf_string(dir));
|
||||||
|
if (isdir && dir_sep != 0) {
|
||||||
|
sbuf_append_char(dir_prefix,dir_sep);
|
||||||
|
}
|
||||||
|
sbuf_delete_from(dir,dlen); // restore dir
|
||||||
|
}
|
||||||
|
if (isdir || match_extension(name, extensions)) {
|
||||||
|
// add completion
|
||||||
|
sbuf_clear(display);
|
||||||
|
ls_colorize(cenv->env->no_lscolors, display, ft, name, NULL, (isdir ? dir_sep : 0));
|
||||||
|
cont = ic_add_completion_ex(cenv, sbuf_string(dir_prefix), sbuf_string(display), NULL);
|
||||||
|
}
|
||||||
|
sbuf_delete_from( dir_prefix, plen ); // restore dir_prefix
|
||||||
|
}
|
||||||
|
} while (cont && os_findnext(d, &entry));
|
||||||
|
os_findclose(d);
|
||||||
|
}
|
||||||
|
return cont;
|
||||||
|
}
|
||||||
|
|
||||||
|
typedef struct filename_closure_s {
|
||||||
|
const char* roots;
|
||||||
|
const char* extensions;
|
||||||
|
char dir_sep;
|
||||||
|
} filename_closure_t;
|
||||||
|
|
||||||
|
static void filename_completer( ic_completion_env_t* cenv, const char* prefix ) {
|
||||||
|
if (prefix == NULL) return;
|
||||||
|
filename_closure_t* fclosure = (filename_closure_t*)cenv->arg;
|
||||||
|
stringbuf_t* root_dir = sbuf_new(cenv->env->mem);
|
||||||
|
stringbuf_t* dir_prefix = sbuf_new(cenv->env->mem);
|
||||||
|
stringbuf_t* display = sbuf_new(cenv->env->mem);
|
||||||
|
if (root_dir!=NULL && dir_prefix != NULL && display != NULL)
|
||||||
|
{
|
||||||
|
// split prefix in dir_prefix / base.
|
||||||
|
const char* base = strrchr(prefix,'/');
|
||||||
|
#ifdef _WIN32
|
||||||
|
const char* base2 = strrchr(prefix,'\\');
|
||||||
|
if (base == NULL || base2 > base) base = base2;
|
||||||
|
#endif
|
||||||
|
if (base != NULL) {
|
||||||
|
base++;
|
||||||
|
sbuf_append_n(dir_prefix, prefix, base - prefix ); // includes dir separator
|
||||||
|
}
|
||||||
|
|
||||||
|
// absolute path
|
||||||
|
if (os_path_is_absolute(prefix)) {
|
||||||
|
// do not use roots but try to complete directly
|
||||||
|
if (base != NULL) {
|
||||||
|
sbuf_append_n( root_dir, prefix, (base - prefix)); // include dir separator
|
||||||
|
}
|
||||||
|
filename_complete_indir( cenv, root_dir, dir_prefix, display,
|
||||||
|
(base != NULL ? base : prefix),
|
||||||
|
fclosure->dir_sep, fclosure->extensions );
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// relative path, complete with respect to every root.
|
||||||
|
const char* next;
|
||||||
|
const char* root = fclosure->roots;
|
||||||
|
while ( root != NULL ) {
|
||||||
|
// create full root in `root_dir`
|
||||||
|
sbuf_clear(root_dir);
|
||||||
|
next = strchr(root,';');
|
||||||
|
if (next == NULL) {
|
||||||
|
sbuf_append( root_dir, root );
|
||||||
|
root = NULL;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
sbuf_append_n( root_dir, root, next - root );
|
||||||
|
root = next + 1;
|
||||||
|
}
|
||||||
|
sbuf_append_char( root_dir, ic_dirsep());
|
||||||
|
|
||||||
|
// add the dir_prefix to the root
|
||||||
|
if (base != NULL) {
|
||||||
|
sbuf_append_n( root_dir, prefix, (base - prefix) - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// and complete in this directory
|
||||||
|
filename_complete_indir( cenv, root_dir, dir_prefix, display,
|
||||||
|
(base != NULL ? base : prefix),
|
||||||
|
fclosure->dir_sep, fclosure->extensions);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sbuf_free(display);
|
||||||
|
sbuf_free(root_dir);
|
||||||
|
sbuf_free(dir_prefix);
|
||||||
|
}
|
||||||
|
|
||||||
|
ic_public void ic_complete_filename( ic_completion_env_t* cenv, const char* prefix, char dir_sep, const char* roots, const char* extensions ) {
|
||||||
|
if (roots == NULL) roots = ".";
|
||||||
|
if (extensions == NULL) extensions = "";
|
||||||
|
if (dir_sep == 0) dir_sep = ic_dirsep();
|
||||||
|
filename_closure_t fclosure;
|
||||||
|
fclosure.dir_sep = dir_sep;
|
||||||
|
fclosure.roots = roots;
|
||||||
|
fclosure.extensions = extensions;
|
||||||
|
cenv->arg = &fclosure;
|
||||||
|
ic_complete_qword_ex( cenv, prefix, &filename_completer, &ic_char_is_filename_letter, '\\', "'\"");
|
||||||
|
}
|
326
deps/isocline/src/completions.c
vendored
Normal file
326
deps/isocline/src/completions.c
vendored
Normal file
@ -0,0 +1,326 @@
|
|||||||
|
/* ----------------------------------------------------------------------------
|
||||||
|
Copyright (c) 2021, Daan Leijen
|
||||||
|
This is free software; you can redistribute it and/or modify it
|
||||||
|
under the terms of the MIT License. A copy of the license can be
|
||||||
|
found in the "LICENSE" file at the root of this distribution.
|
||||||
|
-----------------------------------------------------------------------------*/
|
||||||
|
#include <string.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
#include "../include/isocline.h"
|
||||||
|
#include "common.h"
|
||||||
|
#include "env.h"
|
||||||
|
#include "stringbuf.h"
|
||||||
|
#include "completions.h"
|
||||||
|
|
||||||
|
|
||||||
|
//-------------------------------------------------------------
|
||||||
|
// Completions
|
||||||
|
//-------------------------------------------------------------
|
||||||
|
|
||||||
|
typedef struct completion_s {
|
||||||
|
const char* replacement;
|
||||||
|
const char* display;
|
||||||
|
const char* help;
|
||||||
|
ssize_t delete_before;
|
||||||
|
ssize_t delete_after;
|
||||||
|
} completion_t;
|
||||||
|
|
||||||
|
struct completions_s {
|
||||||
|
ic_completer_fun_t* completer;
|
||||||
|
void* completer_arg;
|
||||||
|
ssize_t completer_max;
|
||||||
|
ssize_t count;
|
||||||
|
ssize_t len;
|
||||||
|
completion_t* elems;
|
||||||
|
alloc_t* mem;
|
||||||
|
};
|
||||||
|
|
||||||
|
static void default_filename_completer( ic_completion_env_t* cenv, const char* prefix );
|
||||||
|
|
||||||
|
ic_private completions_t* completions_new(alloc_t* mem) {
|
||||||
|
completions_t* cms = mem_zalloc_tp(mem, completions_t);
|
||||||
|
if (cms == NULL) return NULL;
|
||||||
|
cms->mem = mem;
|
||||||
|
cms->completer = &default_filename_completer;
|
||||||
|
return cms;
|
||||||
|
}
|
||||||
|
|
||||||
|
ic_private void completions_free(completions_t* cms) {
|
||||||
|
if (cms == NULL) return;
|
||||||
|
completions_clear(cms);
|
||||||
|
if (cms->elems != NULL) {
|
||||||
|
mem_free(cms->mem, cms->elems);
|
||||||
|
cms->elems = NULL;
|
||||||
|
cms->count = 0;
|
||||||
|
cms->len = 0;
|
||||||
|
}
|
||||||
|
mem_free(cms->mem, cms); // free ourselves
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
ic_private void completions_clear(completions_t* cms) {
|
||||||
|
while (cms->count > 0) {
|
||||||
|
completion_t* cm = cms->elems + cms->count - 1;
|
||||||
|
mem_free( cms->mem, cm->display);
|
||||||
|
mem_free( cms->mem, cm->replacement);
|
||||||
|
mem_free( cms->mem, cm->help);
|
||||||
|
memset(cm,0,sizeof(*cm));
|
||||||
|
cms->count--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void completions_push(completions_t* cms, const char* replacement, const char* display, const char* help, ssize_t delete_before, ssize_t delete_after)
|
||||||
|
{
|
||||||
|
if (cms->count >= cms->len) {
|
||||||
|
ssize_t newlen = (cms->len <= 0 ? 32 : cms->len*2);
|
||||||
|
completion_t* newelems = mem_realloc_tp(cms->mem, completion_t, cms->elems, newlen );
|
||||||
|
if (newelems == NULL) return;
|
||||||
|
cms->elems = newelems;
|
||||||
|
cms->len = newlen;
|
||||||
|
}
|
||||||
|
assert(cms->count < cms->len);
|
||||||
|
completion_t* cm = cms->elems + cms->count;
|
||||||
|
cm->replacement = mem_strdup(cms->mem,replacement);
|
||||||
|
cm->display = mem_strdup(cms->mem,display);
|
||||||
|
cm->help = mem_strdup(cms->mem,help);
|
||||||
|
cm->delete_before = delete_before;
|
||||||
|
cm->delete_after = delete_after;
|
||||||
|
cms->count++;
|
||||||
|
}
|
||||||
|
|
||||||
|
ic_private ssize_t completions_count(completions_t* cms) {
|
||||||
|
return cms->count;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool completions_contains(completions_t* cms, const char* replacement) {
|
||||||
|
for( ssize_t i = 0; i < cms->count; i++ ) {
|
||||||
|
const completion_t* c = cms->elems + i;
|
||||||
|
if (strcmp(replacement,c->replacement) == 0) { return true; }
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
ic_private bool completions_add(completions_t* cms, const char* replacement, const char* display, const char* help, ssize_t delete_before, ssize_t delete_after) {
|
||||||
|
if (cms->completer_max <= 0) return false;
|
||||||
|
cms->completer_max--;
|
||||||
|
//debug_msg("completion: add: %d,%d, %s\n", delete_before, delete_after, replacement);
|
||||||
|
if (!completions_contains(cms,replacement)) {
|
||||||
|
completions_push(cms, replacement, display, help, delete_before, delete_after);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static completion_t* completions_get(completions_t* cms, ssize_t index) {
|
||||||
|
if (index < 0 || cms->count <= 0 || index >= cms->count) return NULL;
|
||||||
|
return &cms->elems[index];
|
||||||
|
}
|
||||||
|
|
||||||
|
ic_private const char* completions_get_display( completions_t* cms, ssize_t index, const char** help ) {
|
||||||
|
if (help != NULL) { *help = NULL; }
|
||||||
|
completion_t* cm = completions_get(cms, index);
|
||||||
|
if (cm == NULL) return NULL;
|
||||||
|
if (help != NULL) { *help = cm->help; }
|
||||||
|
return (cm->display != NULL ? cm->display : cm->replacement);
|
||||||
|
}
|
||||||
|
|
||||||
|
ic_private const char* completions_get_help( completions_t* cms, ssize_t index ) {
|
||||||
|
completion_t* cm = completions_get(cms, index);
|
||||||
|
if (cm == NULL) return NULL;
|
||||||
|
return cm->help;
|
||||||
|
}
|
||||||
|
|
||||||
|
ic_private const char* completions_get_hint(completions_t* cms, ssize_t index, const char** help) {
|
||||||
|
if (help != NULL) { *help = NULL; }
|
||||||
|
completion_t* cm = completions_get(cms, index);
|
||||||
|
if (cm == NULL) return NULL;
|
||||||
|
ssize_t len = ic_strlen(cm->replacement);
|
||||||
|
if (len < cm->delete_before) return NULL;
|
||||||
|
const char* hint = (cm->replacement + cm->delete_before);
|
||||||
|
if (*hint == 0 || utf8_is_cont((uint8_t)(*hint))) return NULL; // utf8 boundary?
|
||||||
|
if (help != NULL) { *help = cm->help; }
|
||||||
|
return hint;
|
||||||
|
}
|
||||||
|
|
||||||
|
ic_private void completions_set_completer(completions_t* cms, ic_completer_fun_t* completer, void* arg) {
|
||||||
|
cms->completer = completer;
|
||||||
|
cms->completer_arg = arg;
|
||||||
|
}
|
||||||
|
|
||||||
|
ic_private void completions_get_completer(completions_t* cms, ic_completer_fun_t** completer, void** arg) {
|
||||||
|
*completer = cms->completer;
|
||||||
|
*arg = cms->completer_arg;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
ic_public void* ic_completion_arg( const ic_completion_env_t* cenv ) {
|
||||||
|
return (cenv == NULL ? NULL : cenv->env->completions->completer_arg);
|
||||||
|
}
|
||||||
|
|
||||||
|
ic_public bool ic_has_completions( const ic_completion_env_t* cenv ) {
|
||||||
|
return (cenv == NULL ? false : cenv->env->completions->count > 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
ic_public bool ic_stop_completing( const ic_completion_env_t* cenv) {
|
||||||
|
return (cenv == NULL ? true : cenv->env->completions->completer_max <= 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static ssize_t completion_apply( completion_t* cm, stringbuf_t* sbuf, ssize_t pos ) {
|
||||||
|
if (cm == NULL) return -1;
|
||||||
|
debug_msg( "completion: apply: %s at %zd\n", cm->replacement, pos);
|
||||||
|
ssize_t start = pos - cm->delete_before;
|
||||||
|
if (start < 0) start = 0;
|
||||||
|
ssize_t n = cm->delete_before + cm->delete_after;
|
||||||
|
if (ic_strlen(cm->replacement) == n && strncmp(sbuf_string_at(sbuf,start), cm->replacement, to_size_t(n)) == 0) {
|
||||||
|
// no changes
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
sbuf_delete_from_to( sbuf, start, pos + cm->delete_after );
|
||||||
|
return sbuf_insert_at(sbuf, cm->replacement, start);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ic_private ssize_t completions_apply( completions_t* cms, ssize_t index, stringbuf_t* sbuf, ssize_t pos ) {
|
||||||
|
completion_t* cm = completions_get(cms, index);
|
||||||
|
return completion_apply( cm, sbuf, pos );
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static int completion_compare(const void* p1, const void* p2) {
|
||||||
|
if (p1 == NULL || p2 == NULL) return 0;
|
||||||
|
const completion_t* cm1 = (const completion_t*)p1;
|
||||||
|
const completion_t* cm2 = (const completion_t*)p2;
|
||||||
|
return ic_stricmp(cm1->replacement, cm2->replacement);
|
||||||
|
}
|
||||||
|
|
||||||
|
ic_private void completions_sort(completions_t* cms) {
|
||||||
|
if (cms->count <= 0) return;
|
||||||
|
qsort(cms->elems, to_size_t(cms->count), sizeof(cms->elems[0]), &completion_compare);
|
||||||
|
}
|
||||||
|
|
||||||
|
#define IC_MAX_PREFIX (256)
|
||||||
|
|
||||||
|
// find longest common prefix and complete with that.
|
||||||
|
ic_private ssize_t completions_apply_longest_prefix(completions_t* cms, stringbuf_t* sbuf, ssize_t pos) {
|
||||||
|
if (cms->count <= 1) {
|
||||||
|
return completions_apply(cms,0,sbuf,pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
// set initial prefix to the first entry
|
||||||
|
completion_t* cm = completions_get(cms, 0);
|
||||||
|
if (cm == NULL) return -1;
|
||||||
|
|
||||||
|
char prefix[IC_MAX_PREFIX+1];
|
||||||
|
ssize_t delete_before = cm->delete_before;
|
||||||
|
ic_strncpy( prefix, IC_MAX_PREFIX+1, cm->replacement, IC_MAX_PREFIX );
|
||||||
|
prefix[IC_MAX_PREFIX] = 0;
|
||||||
|
|
||||||
|
// and visit all others to find the longest common prefix
|
||||||
|
for(ssize_t i = 1; i < cms->count; i++) {
|
||||||
|
cm = completions_get(cms,i);
|
||||||
|
if (cm->delete_before != delete_before) { // deletions must match delete_before
|
||||||
|
prefix[0] = 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// check if it is still a prefix
|
||||||
|
const char* r = cm->replacement;
|
||||||
|
ssize_t j;
|
||||||
|
for(j = 0; prefix[j] != 0 && r[j] != 0; j++) {
|
||||||
|
if (prefix[j] != r[j]) break;
|
||||||
|
}
|
||||||
|
prefix[j] = 0;
|
||||||
|
if (j <= 0) break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// check the length
|
||||||
|
ssize_t len = ic_strlen(prefix);
|
||||||
|
if (len <= 0 || len < delete_before) return -1;
|
||||||
|
|
||||||
|
// we found a prefix :-)
|
||||||
|
completion_t cprefix;
|
||||||
|
memset(&cprefix,0,sizeof(cprefix));
|
||||||
|
cprefix.delete_before = delete_before;
|
||||||
|
cprefix.replacement = prefix;
|
||||||
|
ssize_t newpos = completion_apply( &cprefix, sbuf, pos);
|
||||||
|
if (newpos < 0) return newpos;
|
||||||
|
|
||||||
|
// adjust all delete_before for the new replacement
|
||||||
|
for( ssize_t i = 0; i < cms->count; i++) {
|
||||||
|
cm = completions_get(cms,i);
|
||||||
|
cm->delete_before = len;
|
||||||
|
}
|
||||||
|
|
||||||
|
return newpos;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//-------------------------------------------------------------
|
||||||
|
// Completer functions
|
||||||
|
//-------------------------------------------------------------
|
||||||
|
|
||||||
|
ic_public bool ic_add_completions(ic_completion_env_t* cenv, const char* prefix, const char** completions) {
|
||||||
|
for (const char** pc = completions; *pc != NULL; pc++) {
|
||||||
|
if (ic_istarts_with(*pc, prefix)) {
|
||||||
|
if (!ic_add_completion_ex(cenv, *pc, NULL, NULL)) return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
ic_public bool ic_add_completion(ic_completion_env_t* cenv, const char* replacement) {
|
||||||
|
return ic_add_completion_ex(cenv, replacement, NULL, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
ic_public bool ic_add_completion_ex( ic_completion_env_t* cenv, const char* replacement, const char* display, const char* help ) {
|
||||||
|
return ic_add_completion_prim(cenv,replacement,display,help,0,0);
|
||||||
|
}
|
||||||
|
|
||||||
|
ic_public bool ic_add_completion_prim(ic_completion_env_t* cenv, const char* replacement, const char* display, const char* help, long delete_before, long delete_after) {
|
||||||
|
return (*cenv->complete)(cenv->env, cenv->closure, replacement, display, help, delete_before, delete_after );
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool prim_add_completion(ic_env_t* env, void* funenv, const char* replacement, const char* display, const char* help, long delete_before, long delete_after) {
|
||||||
|
ic_unused(funenv);
|
||||||
|
return completions_add(env->completions, replacement, display, help, delete_before, delete_after);
|
||||||
|
}
|
||||||
|
|
||||||
|
ic_public void ic_set_default_completer(ic_completer_fun_t* completer, void* arg) {
|
||||||
|
ic_env_t* env = ic_get_env(); if (env == NULL) return;
|
||||||
|
completions_set_completer(env->completions, completer, arg);
|
||||||
|
}
|
||||||
|
|
||||||
|
ic_private ssize_t completions_generate(struct ic_env_s* env, completions_t* cms, const char* input, ssize_t pos, ssize_t max) {
|
||||||
|
completions_clear(cms);
|
||||||
|
if (cms->completer == NULL || input == NULL || ic_strlen(input) < pos) return 0;
|
||||||
|
|
||||||
|
// set up env
|
||||||
|
ic_completion_env_t cenv;
|
||||||
|
cenv.env = env;
|
||||||
|
cenv.input = input,
|
||||||
|
cenv.cursor = (long)pos;
|
||||||
|
cenv.arg = cms->completer_arg;
|
||||||
|
cenv.complete = &prim_add_completion;
|
||||||
|
cenv.closure = NULL;
|
||||||
|
const char* prefix = mem_strndup(cms->mem, input, pos);
|
||||||
|
cms->completer_max = max;
|
||||||
|
|
||||||
|
// and complete
|
||||||
|
cms->completer(&cenv,prefix);
|
||||||
|
|
||||||
|
// restore
|
||||||
|
mem_free(cms->mem,prefix);
|
||||||
|
return completions_count(cms);
|
||||||
|
}
|
||||||
|
|
||||||
|
// The default completer is no completion is set
|
||||||
|
static void default_filename_completer( ic_completion_env_t* cenv, const char* prefix ) {
|
||||||
|
#ifdef _WIN32
|
||||||
|
const char sep = '\\';
|
||||||
|
#else
|
||||||
|
const char sep = '/';
|
||||||
|
#endif
|
||||||
|
ic_complete_filename( cenv, prefix, sep, ".", NULL);
|
||||||
|
}
|
52
deps/isocline/src/completions.h
vendored
Normal file
52
deps/isocline/src/completions.h
vendored
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
/* ----------------------------------------------------------------------------
|
||||||
|
Copyright (c) 2021, Daan Leijen
|
||||||
|
This is free software; you can redistribute it and/or modify it
|
||||||
|
under the terms of the MIT License. A copy of the license can be
|
||||||
|
found in the "LICENSE" file at the root of this distribution.
|
||||||
|
-----------------------------------------------------------------------------*/
|
||||||
|
#pragma once
|
||||||
|
#ifndef IC_COMPLETIONS_H
|
||||||
|
#define IC_COMPLETIONS_H
|
||||||
|
|
||||||
|
#include "common.h"
|
||||||
|
#include "stringbuf.h"
|
||||||
|
|
||||||
|
|
||||||
|
//-------------------------------------------------------------
|
||||||
|
// Completions
|
||||||
|
//-------------------------------------------------------------
|
||||||
|
#define IC_MAX_COMPLETIONS_TO_SHOW (1000)
|
||||||
|
#define IC_MAX_COMPLETIONS_TO_TRY (IC_MAX_COMPLETIONS_TO_SHOW/4)
|
||||||
|
|
||||||
|
typedef struct completions_s completions_t;
|
||||||
|
|
||||||
|
ic_private completions_t* completions_new(alloc_t* mem);
|
||||||
|
ic_private void completions_free(completions_t* cms);
|
||||||
|
ic_private void completions_clear(completions_t* cms);
|
||||||
|
ic_private bool completions_add(completions_t* cms , const char* replacement, const char* display, const char* help, ssize_t delete_before, ssize_t delete_after);
|
||||||
|
ic_private ssize_t completions_count(completions_t* cms);
|
||||||
|
ic_private ssize_t completions_generate(struct ic_env_s* env, completions_t* cms , const char* input, ssize_t pos, ssize_t max);
|
||||||
|
ic_private void completions_sort(completions_t* cms);
|
||||||
|
ic_private void completions_set_completer(completions_t* cms, ic_completer_fun_t* completer, void* arg);
|
||||||
|
ic_private const char* completions_get_display(completions_t* cms , ssize_t index, const char** help);
|
||||||
|
ic_private const char* completions_get_hint(completions_t* cms, ssize_t index, const char** help);
|
||||||
|
ic_private void completions_get_completer(completions_t* cms, ic_completer_fun_t** completer, void** arg);
|
||||||
|
|
||||||
|
ic_private ssize_t completions_apply(completions_t* cms, ssize_t index, stringbuf_t* sbuf, ssize_t pos);
|
||||||
|
ic_private ssize_t completions_apply_longest_prefix(completions_t* cms, stringbuf_t* sbuf, ssize_t pos);
|
||||||
|
|
||||||
|
//-------------------------------------------------------------
|
||||||
|
// Completion environment
|
||||||
|
//-------------------------------------------------------------
|
||||||
|
typedef bool (ic_completion_fun_t)( ic_env_t* env, void* funenv, const char* replacement, const char* display, const char* help, long delete_before, long delete_after );
|
||||||
|
|
||||||
|
struct ic_completion_env_s {
|
||||||
|
ic_env_t* env; // the isocline environment
|
||||||
|
const char* input; // current full input
|
||||||
|
long cursor; // current cursor position
|
||||||
|
void* arg; // argument given to `ic_set_completer`
|
||||||
|
void* closure; // free variables for function composition
|
||||||
|
ic_completion_fun_t* complete; // function that adds a completion
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // IC_COMPLETIONS_H
|
1142
deps/isocline/src/editline.c
vendored
Normal file
1142
deps/isocline/src/editline.c
vendored
Normal file
File diff suppressed because it is too large
Load Diff
277
deps/isocline/src/editline_completion.c
vendored
Normal file
277
deps/isocline/src/editline_completion.c
vendored
Normal file
@ -0,0 +1,277 @@
|
|||||||
|
/* ----------------------------------------------------------------------------
|
||||||
|
Copyright (c) 2021, Daan Leijen
|
||||||
|
This is free software; you can redistribute it and/or modify it
|
||||||
|
under the terms of the MIT License. A copy of the license can be
|
||||||
|
found in the "LICENSE" file at the root of this distribution.
|
||||||
|
-----------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
//-------------------------------------------------------------
|
||||||
|
// Completion menu: this file is included in editline.c
|
||||||
|
//-------------------------------------------------------------
|
||||||
|
|
||||||
|
// return true if anything changed
|
||||||
|
static bool edit_complete(ic_env_t* env, editor_t* eb, ssize_t idx) {
|
||||||
|
editor_start_modify(eb);
|
||||||
|
ssize_t newpos = completions_apply(env->completions, idx, eb->input, eb->pos);
|
||||||
|
if (newpos < 0) {
|
||||||
|
editor_undo_restore(eb,false);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
eb->pos = newpos;
|
||||||
|
edit_refresh(env,eb);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool edit_complete_longest_prefix(ic_env_t* env, editor_t* eb ) {
|
||||||
|
editor_start_modify(eb);
|
||||||
|
ssize_t newpos = completions_apply_longest_prefix( env->completions, eb->input, eb->pos );
|
||||||
|
if (newpos < 0) {
|
||||||
|
editor_undo_restore(eb,false);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
eb->pos = newpos;
|
||||||
|
edit_refresh(env,eb);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
ic_private void sbuf_append_tagged( stringbuf_t* sb, const char* tag, const char* content ) {
|
||||||
|
sbuf_appendf(sb, "[%s]", tag);
|
||||||
|
sbuf_append(sb,content);
|
||||||
|
sbuf_append(sb,"[/]");
|
||||||
|
}
|
||||||
|
|
||||||
|
static void editor_append_completion(ic_env_t* env, editor_t* eb, ssize_t idx, ssize_t width, bool numbered, bool selected ) {
|
||||||
|
const char* help = NULL;
|
||||||
|
const char* display = completions_get_display(env->completions, idx, &help);
|
||||||
|
if (display == NULL) return;
|
||||||
|
if (numbered) {
|
||||||
|
sbuf_appendf(eb->extra, "[ic-info]%s%zd [/]", (selected ? (tty_is_utf8(env->tty) ? "\xE2\x86\x92" : "*") : " "), 1 + idx);
|
||||||
|
width -= 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (width > 0) {
|
||||||
|
sbuf_appendf(eb->extra, "[width=\"%zd;left; ;on\"]", width );
|
||||||
|
}
|
||||||
|
if (selected) {
|
||||||
|
sbuf_append(eb->extra, "[ic-emphasis]");
|
||||||
|
}
|
||||||
|
sbuf_append(eb->extra, display);
|
||||||
|
if (selected) { sbuf_append(eb->extra,"[/ic-emphasis]"); }
|
||||||
|
if (help != NULL) {
|
||||||
|
sbuf_append(eb->extra, " ");
|
||||||
|
sbuf_append_tagged(eb->extra, "ic-info", help );
|
||||||
|
}
|
||||||
|
if (width > 0) { sbuf_append(eb->extra,"[/width]"); }
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2 and 3 column output up to 80 wide
|
||||||
|
#define IC_DISPLAY2_MAX 34
|
||||||
|
#define IC_DISPLAY2_COL (3+IC_DISPLAY2_MAX)
|
||||||
|
#define IC_DISPLAY2_WIDTH (2*IC_DISPLAY2_COL + 2) // 75
|
||||||
|
|
||||||
|
#define IC_DISPLAY3_MAX 21
|
||||||
|
#define IC_DISPLAY3_COL (3+IC_DISPLAY3_MAX)
|
||||||
|
#define IC_DISPLAY3_WIDTH (3*IC_DISPLAY3_COL + 2*2) // 76
|
||||||
|
|
||||||
|
static void editor_append_completion2(ic_env_t* env, editor_t* eb, ssize_t col_width, ssize_t idx1, ssize_t idx2, ssize_t selected ) {
|
||||||
|
editor_append_completion(env, eb, idx1, col_width, true, (idx1 == selected) );
|
||||||
|
sbuf_append( eb->extra, " ");
|
||||||
|
editor_append_completion(env, eb, idx2, col_width, true, (idx2 == selected) );
|
||||||
|
}
|
||||||
|
|
||||||
|
static void editor_append_completion3(ic_env_t* env, editor_t* eb, ssize_t col_width, ssize_t idx1, ssize_t idx2, ssize_t idx3, ssize_t selected ) {
|
||||||
|
editor_append_completion(env, eb, idx1, col_width, true, (idx1 == selected) );
|
||||||
|
sbuf_append( eb->extra, " ");
|
||||||
|
editor_append_completion(env, eb, idx2, col_width, true, (idx2 == selected));
|
||||||
|
sbuf_append( eb->extra, " ");
|
||||||
|
editor_append_completion(env, eb, idx3, col_width, true, (idx3 == selected) );
|
||||||
|
}
|
||||||
|
|
||||||
|
static ssize_t edit_completions_max_width( ic_env_t* env, ssize_t count ) {
|
||||||
|
ssize_t max_width = 0;
|
||||||
|
for( ssize_t i = 0; i < count; i++) {
|
||||||
|
const char* help = NULL;
|
||||||
|
ssize_t w = bbcode_column_width(env->bbcode, completions_get_display(env->completions, i, &help));
|
||||||
|
if (help != NULL) {
|
||||||
|
w += 2 + bbcode_column_width(env->bbcode, help);
|
||||||
|
}
|
||||||
|
if (w > max_width) {
|
||||||
|
max_width = w;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return max_width;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void edit_completion_menu(ic_env_t* env, editor_t* eb, bool more_available) {
|
||||||
|
ssize_t count = completions_count(env->completions);
|
||||||
|
ssize_t count_displayed = count;
|
||||||
|
assert(count > 1);
|
||||||
|
ssize_t selected = (env->complete_nopreview ? 0 : -1); // select first or none
|
||||||
|
ssize_t percolumn = count;
|
||||||
|
|
||||||
|
again:
|
||||||
|
// show first 9 (or 8) completions
|
||||||
|
sbuf_clear(eb->extra);
|
||||||
|
ssize_t twidth = term_get_width(env->term) - 1;
|
||||||
|
ssize_t colwidth;
|
||||||
|
if (count > 3 && ((colwidth = 3 + edit_completions_max_width(env, 9))*3 + 2*2) < twidth) {
|
||||||
|
// display as a 3 column block
|
||||||
|
count_displayed = (count > 9 ? 9 : count);
|
||||||
|
percolumn = 3;
|
||||||
|
for (ssize_t rw = 0; rw < percolumn; rw++) {
|
||||||
|
if (rw > 0) sbuf_append(eb->extra, "\n");
|
||||||
|
editor_append_completion3(env, eb, colwidth, rw, percolumn+rw, (2*percolumn)+rw, selected);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (count > 4 && ((colwidth = 3 + edit_completions_max_width(env, 8))*2 + 2) < twidth) {
|
||||||
|
// display as a 2 column block if some entries are too wide for three columns
|
||||||
|
count_displayed = (count > 8 ? 8 : count);
|
||||||
|
percolumn = (count_displayed <= 6 ? 3 : 4);
|
||||||
|
for (ssize_t rw = 0; rw < percolumn; rw++) {
|
||||||
|
if (rw > 0) sbuf_append(eb->extra, "\n");
|
||||||
|
editor_append_completion2(env, eb, colwidth, rw, percolumn+rw, selected);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// display as a list
|
||||||
|
count_displayed = (count > 9 ? 9 : count);
|
||||||
|
percolumn = count_displayed;
|
||||||
|
for (ssize_t i = 0; i < count_displayed; i++) {
|
||||||
|
if (i > 0) sbuf_append(eb->extra, "\n");
|
||||||
|
editor_append_completion(env, eb, i, -1, true /* numbered */, selected == i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (count > count_displayed) {
|
||||||
|
if (more_available) {
|
||||||
|
sbuf_append(eb->extra, "\n[ic-info](press page-down (or ctrl-j) to see all further completions)[/]");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
sbuf_appendf(eb->extra, "\n[ic-info](press page-down (or ctrl-j) to see all %zd completions)[/]", count );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!env->complete_nopreview && selected >= 0 && selected <= count_displayed) {
|
||||||
|
edit_complete(env,eb,selected);
|
||||||
|
editor_undo_restore(eb,false);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
edit_refresh(env, eb);
|
||||||
|
}
|
||||||
|
|
||||||
|
// read here; if not a valid key, push it back and return to main event loop
|
||||||
|
code_t c = tty_read(env->tty);
|
||||||
|
if (tty_term_resize_event(env->tty)) {
|
||||||
|
edit_resize(env, eb);
|
||||||
|
}
|
||||||
|
sbuf_clear(eb->extra);
|
||||||
|
|
||||||
|
// direct selection?
|
||||||
|
if (c >= '1' && c <= '9') {
|
||||||
|
ssize_t i = (c - '1');
|
||||||
|
if (i < count) {
|
||||||
|
selected = i;
|
||||||
|
c = KEY_ENTER;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// process commands
|
||||||
|
if (c == KEY_DOWN || c == KEY_TAB) {
|
||||||
|
selected++;
|
||||||
|
if (selected >= count_displayed) {
|
||||||
|
//term_beep(env->term);
|
||||||
|
selected = 0;
|
||||||
|
}
|
||||||
|
goto again;
|
||||||
|
}
|
||||||
|
else if (c == KEY_UP || c == KEY_SHIFT_TAB) {
|
||||||
|
selected--;
|
||||||
|
if (selected < 0) {
|
||||||
|
selected = count_displayed - 1;
|
||||||
|
//term_beep(env->term);
|
||||||
|
}
|
||||||
|
goto again;
|
||||||
|
}
|
||||||
|
else if (c == KEY_F1) {
|
||||||
|
edit_show_help(env, eb);
|
||||||
|
goto again;
|
||||||
|
}
|
||||||
|
else if (c == KEY_ESC) {
|
||||||
|
completions_clear(env->completions);
|
||||||
|
edit_refresh(env,eb);
|
||||||
|
c = 0; // ignore and return
|
||||||
|
}
|
||||||
|
else if (selected >= 0 && (c == KEY_ENTER || c == KEY_RIGHT || c == KEY_END)) /* || c == KEY_TAB*/ {
|
||||||
|
// select the current entry
|
||||||
|
assert(selected < count);
|
||||||
|
c = 0;
|
||||||
|
edit_complete(env, eb, selected);
|
||||||
|
if (env->complete_autotab) {
|
||||||
|
tty_code_pushback(env->tty,KEY_EVENT_AUTOTAB); // immediately try to complete again
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (!env->complete_nopreview && !code_is_virt_key(c)) {
|
||||||
|
// if in preview mode, select the current entry and exit the menu
|
||||||
|
assert(selected < count);
|
||||||
|
edit_complete(env, eb, selected);
|
||||||
|
}
|
||||||
|
else if ((c == KEY_PAGEDOWN || c == KEY_LINEFEED) && count > 9) {
|
||||||
|
// show all completions
|
||||||
|
c = 0;
|
||||||
|
if (more_available) {
|
||||||
|
// generate all entries (up to the max (= 1000))
|
||||||
|
count = completions_generate(env, env->completions, sbuf_string(eb->input), eb->pos, IC_MAX_COMPLETIONS_TO_SHOW);
|
||||||
|
}
|
||||||
|
rowcol_t rc;
|
||||||
|
edit_get_rowcol(env,eb,&rc);
|
||||||
|
edit_clear(env,eb);
|
||||||
|
edit_write_prompt(env,eb,0,false);
|
||||||
|
term_writeln(env->term, "");
|
||||||
|
for(ssize_t i = 0; i < count; i++) {
|
||||||
|
const char* display = completions_get_display(env->completions, i, NULL);
|
||||||
|
if (display != NULL) {
|
||||||
|
bbcode_println(env->bbcode, display);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (count >= IC_MAX_COMPLETIONS_TO_SHOW) {
|
||||||
|
bbcode_println(env->bbcode, "[ic-info]... and more.[/]");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
bbcode_printf(env->bbcode, "[ic-info](%zd possible completions)[/]\n", count );
|
||||||
|
}
|
||||||
|
for(ssize_t i = 0; i < rc.row+1; i++) {
|
||||||
|
term_write(env->term, " \n");
|
||||||
|
}
|
||||||
|
eb->cur_rows = 0;
|
||||||
|
edit_refresh(env,eb);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
edit_refresh(env,eb);
|
||||||
|
}
|
||||||
|
// done
|
||||||
|
completions_clear(env->completions);
|
||||||
|
if (c != 0) tty_code_pushback(env->tty,c);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void edit_generate_completions(ic_env_t* env, editor_t* eb, bool autotab) {
|
||||||
|
debug_msg( "edit: complete: %zd: %s\n", eb->pos, sbuf_string(eb->input) );
|
||||||
|
if (eb->pos < 0) return;
|
||||||
|
ssize_t count = completions_generate(env, env->completions, sbuf_string(eb->input), eb->pos, IC_MAX_COMPLETIONS_TO_TRY);
|
||||||
|
bool more_available = (count >= IC_MAX_COMPLETIONS_TO_TRY);
|
||||||
|
if (count <= 0) {
|
||||||
|
// no completions
|
||||||
|
if (!autotab) { term_beep(env->term); }
|
||||||
|
}
|
||||||
|
else if (count == 1) {
|
||||||
|
// complete if only one match
|
||||||
|
if (edit_complete(env,eb,0 /*idx*/) && env->complete_autotab) {
|
||||||
|
tty_code_pushback(env->tty,KEY_EVENT_AUTOTAB);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
//term_beep(env->term);
|
||||||
|
if (!more_available) {
|
||||||
|
edit_complete_longest_prefix(env,eb);
|
||||||
|
}
|
||||||
|
completions_sort(env->completions);
|
||||||
|
edit_completion_menu( env, eb, more_available);
|
||||||
|
}
|
||||||
|
}
|
140
deps/isocline/src/editline_help.c
vendored
Normal file
140
deps/isocline/src/editline_help.c
vendored
Normal file
@ -0,0 +1,140 @@
|
|||||||
|
/* ----------------------------------------------------------------------------
|
||||||
|
Copyright (c) 2021, Daan Leijen
|
||||||
|
This is free software; you can redistribute it and/or modify it
|
||||||
|
under the terms of the MIT License. A copy of the license can be
|
||||||
|
found in the "LICENSE" file at the root of this distribution.
|
||||||
|
-----------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
//-------------------------------------------------------------
|
||||||
|
// Help: this is included into editline.c
|
||||||
|
//-------------------------------------------------------------
|
||||||
|
|
||||||
|
static const char* help[] = {
|
||||||
|
"","Navigation:",
|
||||||
|
"left,"
|
||||||
|
"^b", "go one character to the left",
|
||||||
|
"right,"
|
||||||
|
"^f", "go one character to the right",
|
||||||
|
"up", "go one row up, or back in the history",
|
||||||
|
"down", "go one row down, or forward in the history",
|
||||||
|
#ifdef __APPLE__
|
||||||
|
"shift-left",
|
||||||
|
#else
|
||||||
|
"^left",
|
||||||
|
#endif
|
||||||
|
"go to the start of the previous word",
|
||||||
|
#ifdef __APPLE__
|
||||||
|
"shift-right",
|
||||||
|
#else
|
||||||
|
"^right",
|
||||||
|
#endif
|
||||||
|
"go to the end the current word",
|
||||||
|
"home,"
|
||||||
|
"^a", "go to the start of the current line",
|
||||||
|
"end,"
|
||||||
|
"^e", "go to the end of the current line",
|
||||||
|
"pgup,"
|
||||||
|
"^home", "go to the start of the current input",
|
||||||
|
"pgdn,"
|
||||||
|
"^end", "go to the end of the current input",
|
||||||
|
"alt-m", "jump to matching brace",
|
||||||
|
"^p", "go back in the history",
|
||||||
|
"^n", "go forward in the history",
|
||||||
|
"^r,^s", "search the history starting with the current word",
|
||||||
|
"","",
|
||||||
|
|
||||||
|
"", "Deletion:",
|
||||||
|
"del,^d", "delete the current character",
|
||||||
|
"backsp,^h", "delete the previous character",
|
||||||
|
"^w", "delete to preceding white space",
|
||||||
|
"alt-backsp", "delete to the start of the current word",
|
||||||
|
"alt-d", "delete to the end of the current word",
|
||||||
|
"^u", "delete to the start of the current line",
|
||||||
|
"^k", "delete to the end of the current line",
|
||||||
|
"esc", "delete the current input, or done with empty input",
|
||||||
|
"","",
|
||||||
|
|
||||||
|
"", "Editing:",
|
||||||
|
"enter", "accept current input",
|
||||||
|
#ifndef __APPLE__
|
||||||
|
"^enter, ^j", "",
|
||||||
|
"shift-tab",
|
||||||
|
#else
|
||||||
|
"shift-tab,^j",
|
||||||
|
#endif
|
||||||
|
"create a new line for multi-line input",
|
||||||
|
//" ", "(or type '\\' followed by enter)",
|
||||||
|
"^l", "clear screen",
|
||||||
|
"^t", "swap with previous character (move character backward)",
|
||||||
|
"^z,^_", "undo",
|
||||||
|
"^y", "redo",
|
||||||
|
//"^C", "done with empty input",
|
||||||
|
//"F1", "show this help",
|
||||||
|
"tab", "try to complete the current input",
|
||||||
|
"","",
|
||||||
|
"","In the completion menu:",
|
||||||
|
"enter,left", "use the currently selected completion",
|
||||||
|
"1 - 9", "use completion N from the menu",
|
||||||
|
"tab,down", "select the next completion",
|
||||||
|
"shift-tab,up","select the previous completion",
|
||||||
|
"esc", "exit menu without completing",
|
||||||
|
"pgdn,^j", "show all further possible completions",
|
||||||
|
"","",
|
||||||
|
"","In incremental history search:",
|
||||||
|
"enter", "use the currently found history entry",
|
||||||
|
"backsp,"
|
||||||
|
"^z", "go back to the previous match (undo)",
|
||||||
|
"tab,"
|
||||||
|
"^r", "find the next match",
|
||||||
|
"shift-tab,"
|
||||||
|
"^s", "find an earlier match",
|
||||||
|
"esc", "exit search",
|
||||||
|
" ","",
|
||||||
|
NULL, NULL
|
||||||
|
};
|
||||||
|
|
||||||
|
static const char* help_initial =
|
||||||
|
"[ic-info]"
|
||||||
|
"Isocline v1.0, copyright (c) 2021 Daan Leijen.\n"
|
||||||
|
"This is free software; you can redistribute it and/or\n"
|
||||||
|
"modify it under the terms of the MIT License.\n"
|
||||||
|
"See <[url]https://github.com/daanx/isocline[/url]> for further information.\n"
|
||||||
|
"We use ^<key> as a shorthand for ctrl-<key>.\n"
|
||||||
|
"\n"
|
||||||
|
"Overview:\n"
|
||||||
|
"\n[ansi-lightgray]"
|
||||||
|
" home,ctrl-a cursor end,ctrl-e\n"
|
||||||
|
" ┌────────────────┼───────────────┐ (navigate)\n"
|
||||||
|
//" │ │ │\n"
|
||||||
|
#ifndef __APPLE__
|
||||||
|
" │ ctrl-left │ ctrl-right │\n"
|
||||||
|
#else
|
||||||
|
" │ alt-left │ alt-right │\n"
|
||||||
|
#endif
|
||||||
|
" │ ┌───────┼──────┐ │ ctrl-r : search history\n"
|
||||||
|
" ▼ ▼ ▼ ▼ ▼ tab : complete word\n"
|
||||||
|
" prompt> [ansi-darkgray]it's the quintessential language[/] shift-tab: insert new line\n"
|
||||||
|
" ▲ ▲ ▲ ▲ esc : delete input, done\n"
|
||||||
|
" │ └──────────────┘ │ ctrl-z : undo\n"
|
||||||
|
" │ alt-backsp alt-d │\n"
|
||||||
|
//" │ │ │\n"
|
||||||
|
" └────────────────────────────────┘ (delete)\n"
|
||||||
|
" ctrl-u ctrl-k\n"
|
||||||
|
"[/ansi-lightgray][/ic-info]\n";
|
||||||
|
|
||||||
|
static void edit_show_help(ic_env_t* env, editor_t* eb) {
|
||||||
|
edit_clear(env, eb);
|
||||||
|
bbcode_println(env->bbcode, help_initial);
|
||||||
|
for (ssize_t i = 0; help[i] != NULL && help[i+1] != NULL; i += 2) {
|
||||||
|
if (help[i][0] == 0) {
|
||||||
|
bbcode_printf(env->bbcode, "[ic-info]%s[/]\n", help[i+1]);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
bbcode_printf(env->bbcode, " [ic-emphasis]%-13s[/][ansi-lightgray]%s%s[/]\n", help[i], (help[i+1][0] == 0 ? "" : ": "), help[i+1]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
eb->cur_rows = 0;
|
||||||
|
eb->cur_row = 0;
|
||||||
|
edit_refresh(env, eb);
|
||||||
|
}
|
260
deps/isocline/src/editline_history.c
vendored
Normal file
260
deps/isocline/src/editline_history.c
vendored
Normal file
@ -0,0 +1,260 @@
|
|||||||
|
/* ----------------------------------------------------------------------------
|
||||||
|
Copyright (c) 2021, Daan Leijen
|
||||||
|
This is free software; you can redistribute it and/or modify it
|
||||||
|
under the terms of the MIT License. A copy of the license can be
|
||||||
|
found in the "LICENSE" file at the root of this distribution.
|
||||||
|
-----------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
//-------------------------------------------------------------
|
||||||
|
// History search: this file is included in editline.c
|
||||||
|
//-------------------------------------------------------------
|
||||||
|
|
||||||
|
static void edit_history_at(ic_env_t* env, editor_t* eb, int ofs )
|
||||||
|
{
|
||||||
|
if (eb->modified) {
|
||||||
|
history_update(env->history, sbuf_string(eb->input)); // update first entry if modified
|
||||||
|
eb->history_idx = 0; // and start again
|
||||||
|
eb->modified = false;
|
||||||
|
}
|
||||||
|
const char* entry = history_get(env->history,eb->history_idx + ofs);
|
||||||
|
// debug_msg( "edit: history: at: %d + %d, found: %s\n", eb->history_idx, ofs, entry);
|
||||||
|
if (entry == NULL) {
|
||||||
|
term_beep(env->term);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
eb->history_idx += ofs;
|
||||||
|
sbuf_replace(eb->input, entry);
|
||||||
|
if (ofs > 0) {
|
||||||
|
// at end of first line when scrolling up
|
||||||
|
ssize_t end = sbuf_find_line_end(eb->input,0);
|
||||||
|
eb->pos = (end < 0 ? 0 : end);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
eb->pos = sbuf_len(eb->input); // at end of last line when scrolling down
|
||||||
|
}
|
||||||
|
edit_refresh(env, eb);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void edit_history_prev(ic_env_t* env, editor_t* eb) {
|
||||||
|
edit_history_at(env,eb, 1 );
|
||||||
|
}
|
||||||
|
|
||||||
|
static void edit_history_next(ic_env_t* env, editor_t* eb) {
|
||||||
|
edit_history_at(env,eb, -1 );
|
||||||
|
}
|
||||||
|
|
||||||
|
typedef struct hsearch_s {
|
||||||
|
struct hsearch_s* next;
|
||||||
|
ssize_t hidx;
|
||||||
|
ssize_t match_pos;
|
||||||
|
ssize_t match_len;
|
||||||
|
bool cinsert;
|
||||||
|
} hsearch_t;
|
||||||
|
|
||||||
|
static void hsearch_push( alloc_t* mem, hsearch_t** hs, ssize_t hidx, ssize_t mpos, ssize_t mlen, bool cinsert ) {
|
||||||
|
hsearch_t* h = mem_zalloc_tp( mem, hsearch_t );
|
||||||
|
if (h == NULL) return;
|
||||||
|
h->hidx = hidx;
|
||||||
|
h->match_pos = mpos;
|
||||||
|
h->match_len = mlen;
|
||||||
|
h->cinsert = cinsert;
|
||||||
|
h->next = *hs;
|
||||||
|
*hs = h;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool hsearch_pop( alloc_t* mem, hsearch_t** hs, ssize_t* hidx, ssize_t* match_pos, ssize_t* match_len, bool* cinsert ) {
|
||||||
|
hsearch_t* h = *hs;
|
||||||
|
if (h == NULL) return false;
|
||||||
|
*hs = h->next;
|
||||||
|
if (hidx != NULL) *hidx = h->hidx;
|
||||||
|
if (match_pos != NULL) *match_pos = h->match_pos;
|
||||||
|
if (match_len != NULL) *match_len = h->match_len;
|
||||||
|
if (cinsert != NULL) *cinsert = h->cinsert;
|
||||||
|
mem_free(mem, h);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void hsearch_done( alloc_t* mem, hsearch_t* hs ) {
|
||||||
|
while (hs != NULL) {
|
||||||
|
hsearch_t* next = hs->next;
|
||||||
|
mem_free(mem, hs);
|
||||||
|
hs = next;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void edit_history_search(ic_env_t* env, editor_t* eb, char* initial ) {
|
||||||
|
if (history_count( env->history ) <= 0) {
|
||||||
|
term_beep(env->term);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// update history
|
||||||
|
if (eb->modified) {
|
||||||
|
history_update(env->history, sbuf_string(eb->input)); // update first entry if modified
|
||||||
|
eb->history_idx = 0; // and start again
|
||||||
|
eb->modified = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// set a search prompt and remember the previous state
|
||||||
|
editor_undo_capture(eb);
|
||||||
|
eb->disable_undo = true;
|
||||||
|
bool old_hint = ic_enable_hint(false);
|
||||||
|
const char* prompt_text = eb->prompt_text;
|
||||||
|
eb->prompt_text = "history search";
|
||||||
|
|
||||||
|
// search state
|
||||||
|
hsearch_t* hs = NULL; // search undo
|
||||||
|
ssize_t hidx = 1; // current history entry
|
||||||
|
ssize_t match_pos = 0; // current matched position
|
||||||
|
ssize_t match_len = 0; // length of the match
|
||||||
|
const char* hentry = NULL; // current history entry
|
||||||
|
|
||||||
|
// Simulate per character searches for each letter in `initial` (so backspace works)
|
||||||
|
if (initial != NULL) {
|
||||||
|
const ssize_t initial_len = ic_strlen(initial);
|
||||||
|
ssize_t ipos = 0;
|
||||||
|
while( ipos < initial_len ) {
|
||||||
|
ssize_t next = str_next_ofs( initial, initial_len, ipos, NULL );
|
||||||
|
if (next < 0) break;
|
||||||
|
hsearch_push( eb->mem, &hs, hidx, match_pos, match_len, true);
|
||||||
|
char c = initial[ipos + next]; // terminate temporarily
|
||||||
|
initial[ipos + next] = 0;
|
||||||
|
if (history_search( env->history, hidx, initial, true, &hidx, &match_pos )) {
|
||||||
|
match_len = ipos + next;
|
||||||
|
}
|
||||||
|
else if (ipos + next >= initial_len) {
|
||||||
|
term_beep(env->term);
|
||||||
|
}
|
||||||
|
initial[ipos + next] = c; // restore
|
||||||
|
ipos += next;
|
||||||
|
}
|
||||||
|
sbuf_replace( eb->input, initial);
|
||||||
|
eb->pos = ipos;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
sbuf_clear( eb->input );
|
||||||
|
eb->pos = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Incremental search
|
||||||
|
again:
|
||||||
|
hentry = history_get(env->history,hidx);
|
||||||
|
if (hentry != NULL) {
|
||||||
|
sbuf_appendf(eb->extra, "[ic-info]%zd. [/][ic-diminish][!pre]", hidx);
|
||||||
|
sbuf_append_n( eb->extra, hentry, match_pos );
|
||||||
|
sbuf_append(eb->extra, "[/pre][u ic-emphasis][!pre]" );
|
||||||
|
sbuf_append_n( eb->extra, hentry + match_pos, match_len );
|
||||||
|
sbuf_append(eb->extra, "[/pre][/u][!pre]" );
|
||||||
|
sbuf_append(eb->extra, hentry + match_pos + match_len );
|
||||||
|
sbuf_append(eb->extra, "[/pre][/ic-diminish]");
|
||||||
|
if (!env->no_help) {
|
||||||
|
sbuf_append(eb->extra, "\n[ic-info](use tab for the next match)[/]");
|
||||||
|
}
|
||||||
|
sbuf_append(eb->extra, "\n" );
|
||||||
|
}
|
||||||
|
edit_refresh(env, eb);
|
||||||
|
|
||||||
|
// Wait for input
|
||||||
|
code_t c = (hentry == NULL ? KEY_ESC : tty_read(env->tty));
|
||||||
|
if (tty_term_resize_event(env->tty)) {
|
||||||
|
edit_resize(env, eb);
|
||||||
|
}
|
||||||
|
sbuf_clear(eb->extra);
|
||||||
|
|
||||||
|
// Process commands
|
||||||
|
if (c == KEY_ESC || c == KEY_BELL /* ^G */ || c == KEY_CTRL_C) {
|
||||||
|
c = 0;
|
||||||
|
eb->disable_undo = false;
|
||||||
|
editor_undo_restore(eb, false);
|
||||||
|
}
|
||||||
|
else if (c == KEY_ENTER) {
|
||||||
|
c = 0;
|
||||||
|
editor_undo_forget(eb);
|
||||||
|
sbuf_replace( eb->input, hentry );
|
||||||
|
eb->pos = sbuf_len(eb->input);
|
||||||
|
eb->modified = false;
|
||||||
|
eb->history_idx = hidx;
|
||||||
|
}
|
||||||
|
else if (c == KEY_BACKSP || c == KEY_CTRL_Z) {
|
||||||
|
// undo last search action
|
||||||
|
bool cinsert;
|
||||||
|
if (hsearch_pop(env->mem,&hs, &hidx, &match_pos, &match_len, &cinsert)) {
|
||||||
|
if (cinsert) edit_backspace(env,eb);
|
||||||
|
}
|
||||||
|
goto again;
|
||||||
|
}
|
||||||
|
else if (c == KEY_CTRL_R || c == KEY_TAB || c == KEY_UP) {
|
||||||
|
// search backward
|
||||||
|
hsearch_push(env->mem, &hs, hidx, match_pos, match_len, false);
|
||||||
|
if (!history_search( env->history, hidx+1, sbuf_string(eb->input), true, &hidx, &match_pos )) {
|
||||||
|
hsearch_pop(env->mem,&hs,NULL,NULL,NULL,NULL);
|
||||||
|
term_beep(env->term);
|
||||||
|
};
|
||||||
|
goto again;
|
||||||
|
}
|
||||||
|
else if (c == KEY_CTRL_S || c == KEY_SHIFT_TAB || c == KEY_DOWN) {
|
||||||
|
// search forward
|
||||||
|
hsearch_push(env->mem, &hs, hidx, match_pos, match_len, false);
|
||||||
|
if (!history_search( env->history, hidx-1, sbuf_string(eb->input), false, &hidx, &match_pos )) {
|
||||||
|
hsearch_pop(env->mem, &hs,NULL,NULL,NULL,NULL);
|
||||||
|
term_beep(env->term);
|
||||||
|
};
|
||||||
|
goto again;
|
||||||
|
}
|
||||||
|
else if (c == KEY_F1) {
|
||||||
|
edit_show_help(env, eb);
|
||||||
|
goto again;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// insert character and search further backward
|
||||||
|
char chr;
|
||||||
|
unicode_t uchr;
|
||||||
|
if (code_is_ascii_char(c,&chr)) {
|
||||||
|
hsearch_push(env->mem, &hs, hidx, match_pos, match_len, true);
|
||||||
|
edit_insert_char(env,eb,chr);
|
||||||
|
}
|
||||||
|
else if (code_is_unicode(c,&uchr)) {
|
||||||
|
hsearch_push(env->mem, &hs, hidx, match_pos, match_len, true);
|
||||||
|
edit_insert_unicode(env,eb,uchr);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// ignore command
|
||||||
|
term_beep(env->term);
|
||||||
|
goto again;
|
||||||
|
}
|
||||||
|
// search for the new input
|
||||||
|
if (history_search( env->history, hidx, sbuf_string(eb->input), true, &hidx, &match_pos )) {
|
||||||
|
match_len = sbuf_len(eb->input);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
term_beep(env->term);
|
||||||
|
};
|
||||||
|
goto again;
|
||||||
|
}
|
||||||
|
|
||||||
|
// done
|
||||||
|
eb->disable_undo = false;
|
||||||
|
hsearch_done(env->mem,hs);
|
||||||
|
eb->prompt_text = prompt_text;
|
||||||
|
ic_enable_hint(old_hint);
|
||||||
|
edit_refresh(env,eb);
|
||||||
|
if (c != 0) tty_code_pushback(env->tty, c);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start an incremental search with the current word
|
||||||
|
static void edit_history_search_with_current_word(ic_env_t* env, editor_t* eb) {
|
||||||
|
char* initial = NULL;
|
||||||
|
ssize_t start = sbuf_find_word_start( eb->input, eb->pos );
|
||||||
|
if (start >= 0) {
|
||||||
|
const ssize_t next = sbuf_next(eb->input, start, NULL);
|
||||||
|
if (!ic_char_is_idletter(sbuf_string(eb->input) + start, (long)(next - start))) {
|
||||||
|
start = next;
|
||||||
|
}
|
||||||
|
if (start >= 0 && start < eb->pos) {
|
||||||
|
initial = mem_strndup(eb->mem, sbuf_string(eb->input) + start, eb->pos - start);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
edit_history_search( env, eb, initial);
|
||||||
|
mem_free(env->mem, initial);
|
||||||
|
}
|
60
deps/isocline/src/env.h
vendored
Normal file
60
deps/isocline/src/env.h
vendored
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
/* ----------------------------------------------------------------------------
|
||||||
|
Copyright (c) 2021, Daan Leijen
|
||||||
|
This is free software; you can redistribute it and/or modify it
|
||||||
|
under the terms of the MIT License. A copy of the license can be
|
||||||
|
found in the "LICENSE" file at the root of this distribution.
|
||||||
|
-----------------------------------------------------------------------------*/
|
||||||
|
#pragma once
|
||||||
|
#ifndef IC_ENV_H
|
||||||
|
#define IC_ENV_H
|
||||||
|
|
||||||
|
#include "../include/isocline.h"
|
||||||
|
#include "common.h"
|
||||||
|
#include "term.h"
|
||||||
|
#include "tty.h"
|
||||||
|
#include "stringbuf.h"
|
||||||
|
#include "history.h"
|
||||||
|
#include "completions.h"
|
||||||
|
#include "bbcode.h"
|
||||||
|
|
||||||
|
//-------------------------------------------------------------
|
||||||
|
// Environment
|
||||||
|
//-------------------------------------------------------------
|
||||||
|
|
||||||
|
struct ic_env_s {
|
||||||
|
alloc_t* mem; // potential custom allocator
|
||||||
|
ic_env_t* next; // next environment (used for proper deallocation)
|
||||||
|
term_t* term; // terminal
|
||||||
|
tty_t* tty; // keyboard (NULL if stdin is a pipe, file, etc)
|
||||||
|
completions_t* completions; // current completions
|
||||||
|
history_t* history; // edit history
|
||||||
|
bbcode_t* bbcode; // print with bbcodes
|
||||||
|
const char* prompt_marker; // the prompt marker (defaults to "> ")
|
||||||
|
const char* cprompt_marker; // prompt marker for continuation lines (defaults to `prompt_marker`)
|
||||||
|
ic_highlight_fun_t* highlighter; // highlight callback
|
||||||
|
void* highlighter_arg; // user state for the highlighter.
|
||||||
|
const char* match_braces; // matching braces, e.g "()[]{}"
|
||||||
|
const char* auto_braces; // auto insertion braces, e.g "()[]{}\"\"''"
|
||||||
|
char multiline_eol; // character used for multiline input ("\") (set to 0 to disable)
|
||||||
|
bool initialized; // are we initialized?
|
||||||
|
bool noedit; // is rich editing possible (tty != NULL)
|
||||||
|
bool singleline_only; // allow only single line editing?
|
||||||
|
bool complete_nopreview; // do not show completion preview for each selection in the completion menu?
|
||||||
|
bool complete_autotab; // try to keep completing after a completion?
|
||||||
|
bool no_multiline_indent; // indent continuation lines to line up under the initial prompt
|
||||||
|
bool no_help; // show short help line for history search etc.
|
||||||
|
bool no_hint; // allow hinting?
|
||||||
|
bool no_highlight; // enable highlighting?
|
||||||
|
bool no_bracematch; // enable brace matching?
|
||||||
|
bool no_autobrace; // enable automatic brace insertion?
|
||||||
|
bool no_lscolors; // use LSCOLORS/LS_COLORS to colorize file name completions?
|
||||||
|
long hint_delay; // delay before displaying a hint in milliseconds
|
||||||
|
};
|
||||||
|
|
||||||
|
ic_private char* ic_editline(ic_env_t* env, const char* prompt_text);
|
||||||
|
|
||||||
|
ic_private ic_env_t* ic_get_env(void);
|
||||||
|
ic_private const char* ic_env_get_auto_braces(ic_env_t* env);
|
||||||
|
ic_private const char* ic_env_get_match_braces(ic_env_t* env);
|
||||||
|
|
||||||
|
#endif // IC_ENV_H
|
259
deps/isocline/src/highlight.c
vendored
Normal file
259
deps/isocline/src/highlight.c
vendored
Normal file
@ -0,0 +1,259 @@
|
|||||||
|
/* ----------------------------------------------------------------------------
|
||||||
|
Copyright (c) 2021, Daan Leijen
|
||||||
|
This is free software; you can redistribute it and/or modify it
|
||||||
|
under the terms of the MIT License. A copy of the license can be
|
||||||
|
found in the "LICENSE" file at the root of this distribution.
|
||||||
|
-----------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
#include <string.h>
|
||||||
|
#include "common.h"
|
||||||
|
#include "term.h"
|
||||||
|
#include "stringbuf.h"
|
||||||
|
#include "attr.h"
|
||||||
|
#include "bbcode.h"
|
||||||
|
|
||||||
|
//-------------------------------------------------------------
|
||||||
|
// Syntax highlighting
|
||||||
|
//-------------------------------------------------------------
|
||||||
|
|
||||||
|
struct ic_highlight_env_s {
|
||||||
|
attrbuf_t* attrs;
|
||||||
|
const char* input;
|
||||||
|
ssize_t input_len;
|
||||||
|
bbcode_t* bbcode;
|
||||||
|
alloc_t* mem;
|
||||||
|
ssize_t cached_upos; // cached unicode position
|
||||||
|
ssize_t cached_cpos; // corresponding utf-8 byte position
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
ic_private void highlight( alloc_t* mem, bbcode_t* bb, const char* s, attrbuf_t* attrs, ic_highlight_fun_t* highlighter, void* arg ) {
|
||||||
|
const ssize_t len = ic_strlen(s);
|
||||||
|
if (len <= 0) return;
|
||||||
|
attrbuf_set_at(attrs,0,len,attr_none()); // fill to length of s
|
||||||
|
if (highlighter != NULL) {
|
||||||
|
ic_highlight_env_t henv;
|
||||||
|
henv.attrs = attrs;
|
||||||
|
henv.input = s;
|
||||||
|
henv.input_len = len;
|
||||||
|
henv.bbcode = bb;
|
||||||
|
henv.mem = mem;
|
||||||
|
henv.cached_cpos = 0;
|
||||||
|
henv.cached_upos = 0;
|
||||||
|
(*highlighter)( &henv, s, arg );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//-------------------------------------------------------------
|
||||||
|
// Client interface
|
||||||
|
//-------------------------------------------------------------
|
||||||
|
|
||||||
|
static void pos_adjust( ic_highlight_env_t* henv, ssize_t* ppos, ssize_t* plen ) {
|
||||||
|
ssize_t pos = *ppos;
|
||||||
|
ssize_t len = *plen;
|
||||||
|
if (pos >= henv->input_len) return;
|
||||||
|
if (pos >= 0 && len >= 0) return; // already character positions
|
||||||
|
if (henv->input == NULL) return;
|
||||||
|
|
||||||
|
if (pos < 0) {
|
||||||
|
// negative `pos` is used as the unicode character position (for easy interfacing with Haskell)
|
||||||
|
ssize_t upos = -pos;
|
||||||
|
ssize_t cpos = 0;
|
||||||
|
ssize_t ucount = 0;
|
||||||
|
if (henv->cached_upos <= upos) { // if we have a cached position, start from there
|
||||||
|
ucount = henv->cached_upos;
|
||||||
|
cpos = henv->cached_cpos;
|
||||||
|
}
|
||||||
|
while ( ucount < upos ) {
|
||||||
|
ssize_t next = str_next_ofs(henv->input, henv->input_len, cpos, NULL);
|
||||||
|
if (next <= 0) return;
|
||||||
|
ucount++;
|
||||||
|
cpos += next;
|
||||||
|
}
|
||||||
|
*ppos = pos = cpos;
|
||||||
|
// and cache it to avoid quadratic behavior
|
||||||
|
henv->cached_upos = upos;
|
||||||
|
henv->cached_cpos = cpos;
|
||||||
|
}
|
||||||
|
if (len < 0) {
|
||||||
|
// negative `len` is used as a unicode character length
|
||||||
|
len = -len;
|
||||||
|
ssize_t ucount = 0;
|
||||||
|
ssize_t clen = 0;
|
||||||
|
while (ucount < len) {
|
||||||
|
ssize_t next = str_next_ofs(henv->input, henv->input_len, pos + clen, NULL);
|
||||||
|
if (next <= 0) return;
|
||||||
|
ucount++;
|
||||||
|
clen += next;
|
||||||
|
}
|
||||||
|
*plen = len = clen;
|
||||||
|
// and update cache if possible
|
||||||
|
if (henv->cached_cpos == pos) {
|
||||||
|
henv->cached_upos += ucount;
|
||||||
|
henv->cached_cpos += clen;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void highlight_attr(ic_highlight_env_t* henv, ssize_t pos, ssize_t count, attr_t attr ) {
|
||||||
|
if (henv==NULL) return;
|
||||||
|
pos_adjust(henv,&pos,&count);
|
||||||
|
if (pos < 0 || count <= 0) return;
|
||||||
|
attrbuf_update_at(henv->attrs, pos, count, attr);
|
||||||
|
}
|
||||||
|
|
||||||
|
ic_public void ic_highlight(ic_highlight_env_t* henv, long pos, long count, const char* style ) {
|
||||||
|
if (henv == NULL || style==NULL || style[0]==0 || pos < 0) return;
|
||||||
|
highlight_attr(henv,pos,count,bbcode_style( henv->bbcode, style ));
|
||||||
|
}
|
||||||
|
|
||||||
|
ic_public void ic_highlight_formatted(ic_highlight_env_t* henv, const char* s, const char* fmt) {
|
||||||
|
if (s==NULL || s[0] == 0 || fmt==NULL) return;
|
||||||
|
attrbuf_t* attrs = attrbuf_new(henv->mem);
|
||||||
|
stringbuf_t* out = sbuf_new(henv->mem); // todo: avoid allocating out?
|
||||||
|
if (attrs!=NULL && out != NULL) {
|
||||||
|
bbcode_append( henv->bbcode, fmt, out, attrs);
|
||||||
|
const ssize_t len = ic_strlen(s);
|
||||||
|
if (sbuf_len(out) != len) {
|
||||||
|
debug_msg("highlight: formatted string content differs from the original input:\n original: %s\n formatted: %s\n", s, fmt);
|
||||||
|
}
|
||||||
|
for( ssize_t i = 0; i < len; i++) {
|
||||||
|
attrbuf_update_at(henv->attrs, i, 1, attrbuf_attr_at(attrs,i));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sbuf_free(out);
|
||||||
|
attrbuf_free(attrs);
|
||||||
|
}
|
||||||
|
|
||||||
|
//-------------------------------------------------------------
|
||||||
|
// Brace matching
|
||||||
|
//-------------------------------------------------------------
|
||||||
|
#define MAX_NESTING (64)
|
||||||
|
|
||||||
|
typedef struct brace_s {
|
||||||
|
char close;
|
||||||
|
bool at_cursor;
|
||||||
|
ssize_t pos;
|
||||||
|
} brace_t;
|
||||||
|
|
||||||
|
ic_private void highlight_match_braces(const char* s, attrbuf_t* attrs, ssize_t cursor_pos, const char* braces, attr_t match_attr, attr_t error_attr)
|
||||||
|
{
|
||||||
|
brace_t open[MAX_NESTING+1];
|
||||||
|
ssize_t nesting = 0;
|
||||||
|
const ssize_t brace_len = ic_strlen(braces);
|
||||||
|
for (long i = 0; i < ic_strlen(s); i++) {
|
||||||
|
const char c = s[i];
|
||||||
|
// push open brace
|
||||||
|
bool found_open = false;
|
||||||
|
for (ssize_t b = 0; b < brace_len; b += 2) {
|
||||||
|
if (c == braces[b]) {
|
||||||
|
// open brace
|
||||||
|
if (nesting >= MAX_NESTING) return; // give up
|
||||||
|
open[nesting].close = braces[b+1];
|
||||||
|
open[nesting].pos = i;
|
||||||
|
open[nesting].at_cursor = (i == cursor_pos - 1);
|
||||||
|
nesting++;
|
||||||
|
found_open = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (found_open) continue;
|
||||||
|
|
||||||
|
// pop to closing brace and potentially highlight
|
||||||
|
for (ssize_t b = 1; b < brace_len; b += 2) {
|
||||||
|
if (c == braces[b]) {
|
||||||
|
// close brace
|
||||||
|
if (nesting <= 0) {
|
||||||
|
// unmatched close brace
|
||||||
|
attrbuf_update_at( attrs, i, 1, error_attr);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// can we fix an unmatched brace where we can match by popping just one?
|
||||||
|
if (open[nesting-1].close != c && nesting > 1 && open[nesting-2].close == c) {
|
||||||
|
// assume previous open brace was wrong
|
||||||
|
attrbuf_update_at(attrs, open[nesting-1].pos, 1, error_attr);
|
||||||
|
nesting--;
|
||||||
|
}
|
||||||
|
if (open[nesting-1].close != c) {
|
||||||
|
// unmatched open brace
|
||||||
|
attrbuf_update_at( attrs, i, 1, error_attr);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// matching brace
|
||||||
|
nesting--;
|
||||||
|
if (i == cursor_pos - 1 || (open[nesting].at_cursor && open[nesting].pos != i - 1)) {
|
||||||
|
// highlight matching brace
|
||||||
|
attrbuf_update_at(attrs, open[nesting].pos, 1, match_attr);
|
||||||
|
attrbuf_update_at(attrs, i, 1, match_attr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// note: don't mark further unmatched open braces as in error
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
ic_private ssize_t find_matching_brace(const char* s, ssize_t cursor_pos, const char* braces, bool* is_balanced)
|
||||||
|
{
|
||||||
|
if (is_balanced != NULL) { *is_balanced = false; }
|
||||||
|
bool balanced = true;
|
||||||
|
ssize_t match = -1;
|
||||||
|
brace_t open[MAX_NESTING+1];
|
||||||
|
ssize_t nesting = 0;
|
||||||
|
const ssize_t brace_len = ic_strlen(braces);
|
||||||
|
for (long i = 0; i < ic_strlen(s); i++) {
|
||||||
|
const char c = s[i];
|
||||||
|
// push open brace
|
||||||
|
bool found_open = false;
|
||||||
|
for (ssize_t b = 0; b < brace_len; b += 2) {
|
||||||
|
if (c == braces[b]) {
|
||||||
|
// open brace
|
||||||
|
if (nesting >= MAX_NESTING) return -1; // give up
|
||||||
|
open[nesting].close = braces[b+1];
|
||||||
|
open[nesting].pos = i;
|
||||||
|
open[nesting].at_cursor = (i == cursor_pos - 1);
|
||||||
|
nesting++;
|
||||||
|
found_open = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (found_open) continue;
|
||||||
|
|
||||||
|
// pop to closing brace
|
||||||
|
for (ssize_t b = 1; b < brace_len; b += 2) {
|
||||||
|
if (c == braces[b]) {
|
||||||
|
// close brace
|
||||||
|
if (nesting <= 0) {
|
||||||
|
// unmatched close brace
|
||||||
|
balanced = false;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (open[nesting-1].close != c) {
|
||||||
|
// unmatched open brace
|
||||||
|
balanced = false;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// matching brace
|
||||||
|
nesting--;
|
||||||
|
if (i == cursor_pos - 1) {
|
||||||
|
// found matching open brace
|
||||||
|
match = open[nesting].pos + 1;
|
||||||
|
}
|
||||||
|
else if (open[nesting].at_cursor) {
|
||||||
|
// found matching close brace
|
||||||
|
match = i + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (nesting != 0) { balanced = false; }
|
||||||
|
if (is_balanced != NULL) { *is_balanced = balanced; }
|
||||||
|
return match;
|
||||||
|
}
|
24
deps/isocline/src/highlight.h
vendored
Normal file
24
deps/isocline/src/highlight.h
vendored
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
/* ----------------------------------------------------------------------------
|
||||||
|
Copyright (c) 2021, Daan Leijen
|
||||||
|
This is free software; you can redistribute it and/or modify it
|
||||||
|
under the terms of the MIT License. A copy of the license can be
|
||||||
|
found in the "LICENSE" file at the root of this distribution.
|
||||||
|
-----------------------------------------------------------------------------*/
|
||||||
|
#pragma once
|
||||||
|
#ifndef IC_HIGHLIGHT_H
|
||||||
|
#define IC_HIGHLIGHT_H
|
||||||
|
|
||||||
|
#include "common.h"
|
||||||
|
#include "attr.h"
|
||||||
|
#include "term.h"
|
||||||
|
#include "bbcode.h"
|
||||||
|
|
||||||
|
//-------------------------------------------------------------
|
||||||
|
// Syntax highlighting
|
||||||
|
//-------------------------------------------------------------
|
||||||
|
|
||||||
|
ic_private void highlight( alloc_t* mem, bbcode_t* bb, const char* s, attrbuf_t* attrs, ic_highlight_fun_t* highlighter, void* arg );
|
||||||
|
ic_private void highlight_match_braces(const char* s, attrbuf_t* attrs, ssize_t cursor_pos, const char* braces, attr_t match_attr, attr_t error_attr);
|
||||||
|
ic_private ssize_t find_matching_brace(const char* s, ssize_t cursor_pos, const char* braces, bool* is_balanced);
|
||||||
|
|
||||||
|
#endif // IC_HIGHLIGHT_H
|
269
deps/isocline/src/history.c
vendored
Normal file
269
deps/isocline/src/history.c
vendored
Normal file
@ -0,0 +1,269 @@
|
|||||||
|
/* ----------------------------------------------------------------------------
|
||||||
|
Copyright (c) 2021, Daan Leijen
|
||||||
|
This is free software; you can redistribute it and/or modify it
|
||||||
|
under the terms of the MIT License. A copy of the license can be
|
||||||
|
found in the "LICENSE" file at the root of this distribution.
|
||||||
|
-----------------------------------------------------------------------------*/
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
|
||||||
|
#include "../include/isocline.h"
|
||||||
|
#include "common.h"
|
||||||
|
#include "history.h"
|
||||||
|
#include "stringbuf.h"
|
||||||
|
|
||||||
|
#define IC_MAX_HISTORY (200)
|
||||||
|
|
||||||
|
struct history_s {
|
||||||
|
ssize_t count; // current number of entries in use
|
||||||
|
ssize_t len; // size of elems
|
||||||
|
const char** elems; // history items (up to count)
|
||||||
|
const char* fname; // history file
|
||||||
|
alloc_t* mem;
|
||||||
|
bool allow_duplicates; // allow duplicate entries?
|
||||||
|
};
|
||||||
|
|
||||||
|
ic_private history_t* history_new(alloc_t* mem) {
|
||||||
|
history_t* h = mem_zalloc_tp(mem,history_t);
|
||||||
|
h->mem = mem;
|
||||||
|
return h;
|
||||||
|
}
|
||||||
|
|
||||||
|
ic_private void history_free(history_t* h) {
|
||||||
|
if (h == NULL) return;
|
||||||
|
history_clear(h);
|
||||||
|
if (h->len > 0) {
|
||||||
|
mem_free( h->mem, h->elems );
|
||||||
|
h->elems = NULL;
|
||||||
|
h->len = 0;
|
||||||
|
}
|
||||||
|
mem_free(h->mem, h->fname);
|
||||||
|
h->fname = NULL;
|
||||||
|
mem_free(h->mem, h); // free ourselves
|
||||||
|
}
|
||||||
|
|
||||||
|
ic_private bool history_enable_duplicates( history_t* h, bool enable ) {
|
||||||
|
bool prev = h->allow_duplicates;
|
||||||
|
h->allow_duplicates = enable;
|
||||||
|
return prev;
|
||||||
|
}
|
||||||
|
|
||||||
|
ic_private ssize_t history_count(const history_t* h) {
|
||||||
|
return h->count;
|
||||||
|
}
|
||||||
|
|
||||||
|
//-------------------------------------------------------------
|
||||||
|
// push/clear
|
||||||
|
//-------------------------------------------------------------
|
||||||
|
|
||||||
|
ic_private bool history_update( history_t* h, const char* entry ) {
|
||||||
|
if (entry==NULL) return false;
|
||||||
|
history_remove_last(h);
|
||||||
|
history_push(h,entry);
|
||||||
|
//debug_msg("history: update: with %s; now at %s\n", entry, history_get(h,0));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void history_delete_at( history_t* h, ssize_t idx ) {
|
||||||
|
if (idx < 0 || idx >= h->count) return;
|
||||||
|
mem_free(h->mem, h->elems[idx]);
|
||||||
|
for(ssize_t i = idx+1; i < h->count; i++) {
|
||||||
|
h->elems[i-1] = h->elems[i];
|
||||||
|
}
|
||||||
|
h->count--;
|
||||||
|
}
|
||||||
|
|
||||||
|
ic_private bool history_push( history_t* h, const char* entry ) {
|
||||||
|
if (h->len <= 0 || entry==NULL) return false;
|
||||||
|
// remove any older duplicate
|
||||||
|
if (!h->allow_duplicates) {
|
||||||
|
for( int i = 0; i < h->count; i++) {
|
||||||
|
if (strcmp(h->elems[i],entry) == 0) {
|
||||||
|
history_delete_at(h,i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// insert at front
|
||||||
|
if (h->count == h->len) {
|
||||||
|
// delete oldest entry
|
||||||
|
history_delete_at(h,0);
|
||||||
|
}
|
||||||
|
assert(h->count < h->len);
|
||||||
|
h->elems[h->count] = mem_strdup(h->mem,entry);
|
||||||
|
h->count++;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void history_remove_last_n( history_t* h, ssize_t n ) {
|
||||||
|
if (n <= 0) return;
|
||||||
|
if (n > h->count) n = h->count;
|
||||||
|
for( ssize_t i = h->count - n; i < h->count; i++) {
|
||||||
|
mem_free( h->mem, h->elems[i] );
|
||||||
|
}
|
||||||
|
h->count -= n;
|
||||||
|
assert(h->count >= 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
ic_private void history_remove_last(history_t* h) {
|
||||||
|
history_remove_last_n(h,1);
|
||||||
|
}
|
||||||
|
|
||||||
|
ic_private void history_clear(history_t* h) {
|
||||||
|
history_remove_last_n( h, h->count );
|
||||||
|
}
|
||||||
|
|
||||||
|
ic_private const char* history_get( const history_t* h, ssize_t n ) {
|
||||||
|
if (n < 0 || n >= h->count) return NULL;
|
||||||
|
return h->elems[h->count - n - 1];
|
||||||
|
}
|
||||||
|
|
||||||
|
ic_private bool history_search( const history_t* h, ssize_t from /*including*/, const char* search, bool backward, ssize_t* hidx, ssize_t* hpos ) {
|
||||||
|
const char* p = NULL;
|
||||||
|
ssize_t i;
|
||||||
|
if (backward) {
|
||||||
|
for( i = from; i < h->count; i++ ) {
|
||||||
|
p = strstr( history_get(h,i), search);
|
||||||
|
if (p != NULL) break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
for( i = from; i >= 0; i-- ) {
|
||||||
|
p = strstr( history_get(h,i), search);
|
||||||
|
if (p != NULL) break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (p == NULL) return false;
|
||||||
|
if (hidx != NULL) *hidx = i;
|
||||||
|
if (hpos != NULL) *hpos = (p - history_get(h,i));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
//-------------------------------------------------------------
|
||||||
|
//
|
||||||
|
//-------------------------------------------------------------
|
||||||
|
|
||||||
|
ic_private void history_load_from(history_t* h, const char* fname, long max_entries ) {
|
||||||
|
history_clear(h);
|
||||||
|
h->fname = mem_strdup(h->mem,fname);
|
||||||
|
if (max_entries == 0) {
|
||||||
|
assert(h->elems == NULL);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (max_entries < 0 || max_entries > IC_MAX_HISTORY) max_entries = IC_MAX_HISTORY;
|
||||||
|
h->elems = (const char**)mem_zalloc_tp_n(h->mem, char*, max_entries );
|
||||||
|
if (h->elems == NULL) return;
|
||||||
|
h->len = max_entries;
|
||||||
|
history_load(h);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//-------------------------------------------------------------
|
||||||
|
// save/load history to file
|
||||||
|
//-------------------------------------------------------------
|
||||||
|
|
||||||
|
static char from_xdigit( int c ) {
|
||||||
|
if (c >= '0' && c <= '9') return (char)(c - '0');
|
||||||
|
if (c >= 'A' && c <= 'F') return (char)(10 + (c - 'A'));
|
||||||
|
if (c >= 'a' && c <= 'f') return (char)(10 + (c - 'a'));
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static char to_xdigit( uint8_t c ) {
|
||||||
|
if (c <= 9) return ((char)c + '0');
|
||||||
|
if (c >= 10 && c <= 15) return ((char)c - 10 + 'A');
|
||||||
|
return '0';
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool ic_isxdigit( int c ) {
|
||||||
|
return ((c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F') || (c >= '0' && c <= '9'));
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool history_read_entry( history_t* h, FILE* f, stringbuf_t* sbuf ) {
|
||||||
|
sbuf_clear(sbuf);
|
||||||
|
while( !feof(f)) {
|
||||||
|
int c = fgetc(f);
|
||||||
|
if (c == EOF || c == '\n') break;
|
||||||
|
if (c == '\\') {
|
||||||
|
c = fgetc(f);
|
||||||
|
if (c == 'n') { sbuf_append(sbuf,"\n"); }
|
||||||
|
else if (c == 'r') { /* ignore */ } // sbuf_append(sbuf,"\r");
|
||||||
|
else if (c == 't') { sbuf_append(sbuf,"\t"); }
|
||||||
|
else if (c == '\\') { sbuf_append(sbuf,"\\"); }
|
||||||
|
else if (c == 'x') {
|
||||||
|
int c1 = fgetc(f);
|
||||||
|
int c2 = fgetc(f);
|
||||||
|
if (ic_isxdigit(c1) && ic_isxdigit(c2)) {
|
||||||
|
char chr = from_xdigit(c1)*16 + from_xdigit(c2);
|
||||||
|
sbuf_append_char(sbuf,chr);
|
||||||
|
}
|
||||||
|
else return false;
|
||||||
|
}
|
||||||
|
else return false;
|
||||||
|
}
|
||||||
|
else sbuf_append_char(sbuf,(char)c);
|
||||||
|
}
|
||||||
|
if (sbuf_len(sbuf)==0 || sbuf_string(sbuf)[0] == '#') return true;
|
||||||
|
return history_push(h, sbuf_string(sbuf));
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool history_write_entry( const char* entry, FILE* f, stringbuf_t* sbuf ) {
|
||||||
|
sbuf_clear(sbuf);
|
||||||
|
//debug_msg("history: write: %s\n", entry);
|
||||||
|
while( entry != NULL && *entry != 0 ) {
|
||||||
|
char c = *entry++;
|
||||||
|
if (c == '\\') { sbuf_append(sbuf,"\\\\"); }
|
||||||
|
else if (c == '\n') { sbuf_append(sbuf,"\\n"); }
|
||||||
|
else if (c == '\r') { /* ignore */ } // sbuf_append(sbuf,"\\r"); }
|
||||||
|
else if (c == '\t') { sbuf_append(sbuf,"\\t"); }
|
||||||
|
else if (c < ' ' || c > '~' || c == '#') {
|
||||||
|
char c1 = to_xdigit( (uint8_t)c / 16 );
|
||||||
|
char c2 = to_xdigit( (uint8_t)c % 16 );
|
||||||
|
sbuf_append(sbuf,"\\x");
|
||||||
|
sbuf_append_char(sbuf,c1);
|
||||||
|
sbuf_append_char(sbuf,c2);
|
||||||
|
}
|
||||||
|
else sbuf_append_char(sbuf,c);
|
||||||
|
}
|
||||||
|
//debug_msg("history: write buf: %s\n", sbuf_string(sbuf));
|
||||||
|
|
||||||
|
if (sbuf_len(sbuf) > 0) {
|
||||||
|
sbuf_append(sbuf,"\n");
|
||||||
|
fputs(sbuf_string(sbuf),f);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
ic_private void history_load( history_t* h ) {
|
||||||
|
if (h->fname == NULL) return;
|
||||||
|
FILE* f = fopen(h->fname, "r");
|
||||||
|
if (f == NULL) return;
|
||||||
|
stringbuf_t* sbuf = sbuf_new(h->mem);
|
||||||
|
if (sbuf != NULL) {
|
||||||
|
while (!feof(f)) {
|
||||||
|
if (!history_read_entry(h,f,sbuf)) break; // error
|
||||||
|
}
|
||||||
|
sbuf_free(sbuf);
|
||||||
|
}
|
||||||
|
fclose(f);
|
||||||
|
}
|
||||||
|
|
||||||
|
ic_private void history_save( const history_t* h ) {
|
||||||
|
if (h->fname == NULL) return;
|
||||||
|
FILE* f = fopen(h->fname, "w");
|
||||||
|
if (f == NULL) return;
|
||||||
|
#ifndef _WIN32
|
||||||
|
chmod(h->fname,S_IRUSR|S_IWUSR);
|
||||||
|
#endif
|
||||||
|
stringbuf_t* sbuf = sbuf_new(h->mem);
|
||||||
|
if (sbuf != NULL) {
|
||||||
|
for( int i = 0; i < h->count; i++ ) {
|
||||||
|
if (!history_write_entry(h->elems[i],f,sbuf)) break; // error
|
||||||
|
}
|
||||||
|
sbuf_free(sbuf);
|
||||||
|
}
|
||||||
|
fclose(f);
|
||||||
|
}
|
38
deps/isocline/src/history.h
vendored
Normal file
38
deps/isocline/src/history.h
vendored
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
/* ----------------------------------------------------------------------------
|
||||||
|
Copyright (c) 2021, Daan Leijen
|
||||||
|
This is free software; you can redistribute it and/or modify it
|
||||||
|
under the terms of the MIT License. A copy of the license can be
|
||||||
|
found in the "LICENSE" file at the root of this distribution.
|
||||||
|
-----------------------------------------------------------------------------*/
|
||||||
|
#pragma once
|
||||||
|
#ifndef IC_HISTORY_H
|
||||||
|
#define IC_HISTORY_H
|
||||||
|
|
||||||
|
#include "common.h"
|
||||||
|
|
||||||
|
//-------------------------------------------------------------
|
||||||
|
// History
|
||||||
|
//-------------------------------------------------------------
|
||||||
|
|
||||||
|
struct history_s;
|
||||||
|
typedef struct history_s history_t;
|
||||||
|
|
||||||
|
ic_private history_t* history_new(alloc_t* mem);
|
||||||
|
ic_private void history_free(history_t* h);
|
||||||
|
ic_private void history_clear(history_t* h);
|
||||||
|
ic_private bool history_enable_duplicates( history_t* h, bool enable );
|
||||||
|
ic_private ssize_t history_count(const history_t* h);
|
||||||
|
|
||||||
|
ic_private void history_load_from(history_t* h, const char* fname, long max_entries);
|
||||||
|
ic_private void history_load( history_t* h );
|
||||||
|
ic_private void history_save( const history_t* h );
|
||||||
|
|
||||||
|
ic_private bool history_push( history_t* h, const char* entry );
|
||||||
|
ic_private bool history_update( history_t* h, const char* entry );
|
||||||
|
ic_private const char* history_get( const history_t* h, ssize_t n );
|
||||||
|
ic_private void history_remove_last(history_t* h);
|
||||||
|
|
||||||
|
ic_private bool history_search( const history_t* h, ssize_t from, const char* search, bool backward, ssize_t* hidx, ssize_t* hpos);
|
||||||
|
|
||||||
|
|
||||||
|
#endif // IC_HISTORY_H
|
594
deps/isocline/src/isocline.c
vendored
Normal file
594
deps/isocline/src/isocline.c
vendored
Normal file
@ -0,0 +1,594 @@
|
|||||||
|
/* ----------------------------------------------------------------------------
|
||||||
|
Copyright (c) 2021, Daan Leijen
|
||||||
|
This is free software; you can redistribute it and/or modify it
|
||||||
|
under the terms of the MIT License. A copy of the license can be
|
||||||
|
found in the "LICENSE" file at the root of this distribution.
|
||||||
|
-----------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
//-------------------------------------------------------------
|
||||||
|
// Usually we include all sources one file so no internal
|
||||||
|
// symbols are public in the libray.
|
||||||
|
//
|
||||||
|
// You can compile the entire library just as:
|
||||||
|
// $ gcc -c src/isocline.c
|
||||||
|
//-------------------------------------------------------------
|
||||||
|
#if !defined(IC_SEPARATE_OBJS)
|
||||||
|
# ifndef _CRT_NONSTDC_NO_WARNINGS
|
||||||
|
# define _CRT_NONSTDC_NO_WARNINGS // for msvc
|
||||||
|
# endif
|
||||||
|
# ifndef _CRT_SECURE_NO_WARNINGS
|
||||||
|
# define _CRT_SECURE_NO_WARNINGS // for msvc
|
||||||
|
# endif
|
||||||
|
# define _XOPEN_SOURCE 700 // for wcwidth
|
||||||
|
# define _DEFAULT_SOURCE // ensure usleep stays visible with _XOPEN_SOURCE >= 700
|
||||||
|
# include "attr.c"
|
||||||
|
# include "bbcode.c"
|
||||||
|
# include "editline.c"
|
||||||
|
# include "highlight.c"
|
||||||
|
# include "undo.c"
|
||||||
|
# include "history.c"
|
||||||
|
# include "completers.c"
|
||||||
|
# include "completions.c"
|
||||||
|
# include "term.c"
|
||||||
|
# include "tty_esc.c"
|
||||||
|
# include "tty.c"
|
||||||
|
# include "stringbuf.c"
|
||||||
|
# include "common.c"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
//-------------------------------------------------------------
|
||||||
|
// includes
|
||||||
|
//-------------------------------------------------------------
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <stdarg.h>
|
||||||
|
|
||||||
|
#include "../include/isocline.h"
|
||||||
|
#include "common.h"
|
||||||
|
#include "env.h"
|
||||||
|
|
||||||
|
|
||||||
|
//-------------------------------------------------------------
|
||||||
|
// Readline
|
||||||
|
//-------------------------------------------------------------
|
||||||
|
|
||||||
|
static char* ic_getline( alloc_t* mem );
|
||||||
|
|
||||||
|
ic_public char* ic_readline(const char* prompt_text)
|
||||||
|
{
|
||||||
|
ic_env_t* env = ic_get_env();
|
||||||
|
if (env == NULL) return NULL;
|
||||||
|
if (!env->noedit) {
|
||||||
|
// terminal editing enabled
|
||||||
|
return ic_editline(env, prompt_text); // in editline.c
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// no editing capability (pipe, dumb terminal, etc)
|
||||||
|
if (env->tty != NULL && env->term != NULL) {
|
||||||
|
// if the terminal is not interactive, but we are reading from the tty (keyboard), we display a prompt
|
||||||
|
term_start_raw(env->term); // set utf8 mode on windows
|
||||||
|
if (prompt_text != NULL) {
|
||||||
|
term_write(env->term, prompt_text);
|
||||||
|
}
|
||||||
|
term_write(env->term, env->prompt_marker);
|
||||||
|
term_end_raw(env->term, false);
|
||||||
|
}
|
||||||
|
// read directly from stdin
|
||||||
|
return ic_getline(env->mem);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//-------------------------------------------------------------
|
||||||
|
// Read a line from the stdin stream if there is no editing
|
||||||
|
// support (like from a pipe, file, or dumb terminal).
|
||||||
|
//-------------------------------------------------------------
|
||||||
|
|
||||||
|
static char* ic_getline(alloc_t* mem)
|
||||||
|
{
|
||||||
|
// read until eof or newline
|
||||||
|
stringbuf_t* sb = sbuf_new(mem);
|
||||||
|
int c;
|
||||||
|
while (true) {
|
||||||
|
c = fgetc(stdin);
|
||||||
|
if (c==EOF || c=='\n') {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
sbuf_append_char(sb, (char)c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return sbuf_free_dup(sb);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//-------------------------------------------------------------
|
||||||
|
// Formatted output
|
||||||
|
//-------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
ic_public void ic_printf(const char* fmt, ...) {
|
||||||
|
va_list ap;
|
||||||
|
va_start(ap, fmt);
|
||||||
|
ic_vprintf(fmt, ap);
|
||||||
|
va_end(ap);
|
||||||
|
}
|
||||||
|
|
||||||
|
ic_public void ic_vprintf(const char* fmt, va_list args) {
|
||||||
|
ic_env_t* env = ic_get_env(); if (env==NULL || env->bbcode == NULL) return;
|
||||||
|
bbcode_vprintf(env->bbcode, fmt, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
ic_public void ic_print(const char* s) {
|
||||||
|
ic_env_t* env = ic_get_env(); if (env==NULL || env->bbcode==NULL) return;
|
||||||
|
bbcode_print(env->bbcode, s);
|
||||||
|
}
|
||||||
|
|
||||||
|
ic_public void ic_println(const char* s) {
|
||||||
|
ic_env_t* env = ic_get_env(); if (env==NULL || env->bbcode==NULL) return;
|
||||||
|
bbcode_println(env->bbcode, s);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ic_style_def(const char* name, const char* fmt) {
|
||||||
|
ic_env_t* env = ic_get_env(); if (env==NULL || env->bbcode==NULL) return;
|
||||||
|
bbcode_style_def(env->bbcode, name, fmt);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ic_style_open(const char* fmt) {
|
||||||
|
ic_env_t* env = ic_get_env(); if (env==NULL || env->bbcode==NULL) return;
|
||||||
|
bbcode_style_open(env->bbcode, fmt);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ic_style_close(void) {
|
||||||
|
ic_env_t* env = ic_get_env(); if (env==NULL || env->bbcode==NULL) return;
|
||||||
|
bbcode_style_close(env->bbcode, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//-------------------------------------------------------------
|
||||||
|
// Interface
|
||||||
|
//-------------------------------------------------------------
|
||||||
|
|
||||||
|
ic_public bool ic_async_stop(void) {
|
||||||
|
ic_env_t* env = ic_get_env(); if (env==NULL) return false;
|
||||||
|
if (env->tty==NULL) return false;
|
||||||
|
return tty_async_stop(env->tty);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void set_prompt_marker(ic_env_t* env, const char* prompt_marker, const char* cprompt_marker) {
|
||||||
|
if (prompt_marker == NULL) prompt_marker = "> ";
|
||||||
|
if (cprompt_marker == NULL) cprompt_marker = prompt_marker;
|
||||||
|
mem_free(env->mem, env->prompt_marker);
|
||||||
|
mem_free(env->mem, env->cprompt_marker);
|
||||||
|
env->prompt_marker = mem_strdup(env->mem, prompt_marker);
|
||||||
|
env->cprompt_marker = mem_strdup(env->mem, cprompt_marker);
|
||||||
|
}
|
||||||
|
|
||||||
|
ic_public const char* ic_get_prompt_marker(void) {
|
||||||
|
ic_env_t* env = ic_get_env(); if (env==NULL) return NULL;
|
||||||
|
return env->prompt_marker;
|
||||||
|
}
|
||||||
|
|
||||||
|
ic_public const char* ic_get_continuation_prompt_marker(void) {
|
||||||
|
ic_env_t* env = ic_get_env(); if (env==NULL) return NULL;
|
||||||
|
return env->cprompt_marker;
|
||||||
|
}
|
||||||
|
|
||||||
|
ic_public void ic_set_prompt_marker( const char* prompt_marker, const char* cprompt_marker ) {
|
||||||
|
ic_env_t* env = ic_get_env(); if (env==NULL) return;
|
||||||
|
set_prompt_marker(env, prompt_marker, cprompt_marker);
|
||||||
|
}
|
||||||
|
|
||||||
|
ic_public bool ic_enable_multiline( bool enable ) {
|
||||||
|
ic_env_t* env = ic_get_env(); if (env==NULL) return false;
|
||||||
|
bool prev = env->singleline_only;
|
||||||
|
env->singleline_only = !enable;
|
||||||
|
return !prev;
|
||||||
|
}
|
||||||
|
|
||||||
|
ic_public bool ic_enable_beep( bool enable ) {
|
||||||
|
ic_env_t* env = ic_get_env(); if (env==NULL) return false;
|
||||||
|
return term_enable_beep(env->term, enable);
|
||||||
|
}
|
||||||
|
|
||||||
|
ic_public bool ic_enable_color( bool enable ) {
|
||||||
|
ic_env_t* env = ic_get_env(); if (env==NULL) return false;
|
||||||
|
return term_enable_color( env->term, enable );
|
||||||
|
}
|
||||||
|
|
||||||
|
ic_public bool ic_enable_history_duplicates( bool enable ) {
|
||||||
|
ic_env_t* env = ic_get_env(); if (env==NULL) return false;
|
||||||
|
return history_enable_duplicates(env->history, enable);
|
||||||
|
}
|
||||||
|
|
||||||
|
ic_public void ic_set_history(const char* fname, long max_entries ) {
|
||||||
|
ic_env_t* env = ic_get_env(); if (env==NULL) return;
|
||||||
|
history_load_from(env->history, fname, max_entries );
|
||||||
|
}
|
||||||
|
|
||||||
|
ic_public void ic_history_remove_last(void) {
|
||||||
|
ic_env_t* env = ic_get_env(); if (env==NULL) return;
|
||||||
|
history_remove_last(env->history);
|
||||||
|
}
|
||||||
|
|
||||||
|
ic_public void ic_history_add( const char* entry ) {
|
||||||
|
ic_env_t* env = ic_get_env(); if (env==NULL) return;
|
||||||
|
history_push( env->history, entry );
|
||||||
|
}
|
||||||
|
|
||||||
|
ic_public void ic_history_clear(void) {
|
||||||
|
ic_env_t* env = ic_get_env(); if (env==NULL) return;
|
||||||
|
history_clear(env->history);
|
||||||
|
}
|
||||||
|
|
||||||
|
ic_public bool ic_enable_auto_tab( bool enable ) {
|
||||||
|
ic_env_t* env = ic_get_env(); if (env==NULL) return false;
|
||||||
|
bool prev = env->complete_autotab;
|
||||||
|
env->complete_autotab = enable;
|
||||||
|
return prev;
|
||||||
|
}
|
||||||
|
|
||||||
|
ic_public bool ic_enable_completion_preview( bool enable ) {
|
||||||
|
ic_env_t* env = ic_get_env(); if (env==NULL) return false;
|
||||||
|
bool prev = env->complete_nopreview;
|
||||||
|
env->complete_nopreview = !enable;
|
||||||
|
return !prev;
|
||||||
|
}
|
||||||
|
|
||||||
|
ic_public bool ic_enable_multiline_indent(bool enable) {
|
||||||
|
ic_env_t* env = ic_get_env(); if (env==NULL) return false;
|
||||||
|
bool prev = env->no_multiline_indent;
|
||||||
|
env->no_multiline_indent = !enable;
|
||||||
|
return !prev;
|
||||||
|
}
|
||||||
|
|
||||||
|
ic_public bool ic_enable_hint(bool enable) {
|
||||||
|
ic_env_t* env = ic_get_env(); if (env==NULL) return false;
|
||||||
|
bool prev = env->no_hint;
|
||||||
|
env->no_hint = !enable;
|
||||||
|
return !prev;
|
||||||
|
}
|
||||||
|
|
||||||
|
ic_public long ic_set_hint_delay(long delay_ms) {
|
||||||
|
ic_env_t* env = ic_get_env(); if (env==NULL) return false;
|
||||||
|
long prev = env->hint_delay;
|
||||||
|
env->hint_delay = (delay_ms < 0 ? 0 : (delay_ms > 5000 ? 5000 : delay_ms));
|
||||||
|
return prev;
|
||||||
|
}
|
||||||
|
|
||||||
|
ic_public void ic_set_tty_esc_delay(long initial_delay_ms, long followup_delay_ms ) {
|
||||||
|
ic_env_t* env = ic_get_env(); if (env==NULL) return;
|
||||||
|
if (env->tty == NULL) return;
|
||||||
|
tty_set_esc_delay(env->tty, initial_delay_ms, followup_delay_ms);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
ic_public bool ic_enable_highlight(bool enable) {
|
||||||
|
ic_env_t* env = ic_get_env(); if (env==NULL) return false;
|
||||||
|
bool prev = env->no_highlight;
|
||||||
|
env->no_highlight = !enable;
|
||||||
|
return !prev;
|
||||||
|
}
|
||||||
|
|
||||||
|
ic_public bool ic_enable_inline_help(bool enable) {
|
||||||
|
ic_env_t* env = ic_get_env(); if (env==NULL) return false;
|
||||||
|
bool prev = env->no_help;
|
||||||
|
env->no_help = !enable;
|
||||||
|
return !prev;
|
||||||
|
}
|
||||||
|
|
||||||
|
ic_public bool ic_enable_brace_matching(bool enable) {
|
||||||
|
ic_env_t* env = ic_get_env(); if (env==NULL) return false;
|
||||||
|
bool prev = env->no_bracematch;
|
||||||
|
env->no_bracematch = !enable;
|
||||||
|
return !prev;
|
||||||
|
}
|
||||||
|
|
||||||
|
ic_public void ic_set_matching_braces(const char* brace_pairs) {
|
||||||
|
ic_env_t* env = ic_get_env(); if (env==NULL) return;
|
||||||
|
mem_free(env->mem, env->match_braces);
|
||||||
|
env->match_braces = NULL;
|
||||||
|
if (brace_pairs != NULL) {
|
||||||
|
ssize_t len = ic_strlen(brace_pairs);
|
||||||
|
if (len > 0 && (len % 2) == 0) {
|
||||||
|
env->match_braces = mem_strdup(env->mem, brace_pairs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ic_public bool ic_enable_brace_insertion(bool enable) {
|
||||||
|
ic_env_t* env = ic_get_env(); if (env==NULL) return false;
|
||||||
|
bool prev = env->no_autobrace;
|
||||||
|
env->no_autobrace = !enable;
|
||||||
|
return !prev;
|
||||||
|
}
|
||||||
|
|
||||||
|
ic_public void ic_set_insertion_braces(const char* brace_pairs) {
|
||||||
|
ic_env_t* env = ic_get_env(); if (env==NULL) return;
|
||||||
|
mem_free(env->mem, env->auto_braces);
|
||||||
|
env->auto_braces = NULL;
|
||||||
|
if (brace_pairs != NULL) {
|
||||||
|
ssize_t len = ic_strlen(brace_pairs);
|
||||||
|
if (len > 0 && (len % 2) == 0) {
|
||||||
|
env->auto_braces = mem_strdup(env->mem, brace_pairs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ic_private const char* ic_env_get_match_braces(ic_env_t* env) {
|
||||||
|
return (env->match_braces == NULL ? "()[]{}" : env->match_braces);
|
||||||
|
}
|
||||||
|
|
||||||
|
ic_private const char* ic_env_get_auto_braces(ic_env_t* env) {
|
||||||
|
return (env->auto_braces == NULL ? "()[]{}\"\"''" : env->auto_braces);
|
||||||
|
}
|
||||||
|
|
||||||
|
ic_public void ic_set_default_highlighter(ic_highlight_fun_t* highlighter, void* arg) {
|
||||||
|
ic_env_t* env = ic_get_env(); if (env==NULL) return;
|
||||||
|
env->highlighter = highlighter;
|
||||||
|
env->highlighter_arg = arg;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
ic_public void ic_free( void* p ) {
|
||||||
|
ic_env_t* env = ic_get_env(); if (env==NULL) return;
|
||||||
|
mem_free(env->mem, p);
|
||||||
|
}
|
||||||
|
|
||||||
|
ic_public void* ic_malloc(size_t sz) {
|
||||||
|
ic_env_t* env = ic_get_env(); if (env==NULL) return NULL;
|
||||||
|
return mem_malloc(env->mem, to_ssize_t(sz));
|
||||||
|
}
|
||||||
|
|
||||||
|
ic_public const char* ic_strdup( const char* s ) {
|
||||||
|
if (s==NULL) return NULL;
|
||||||
|
ic_env_t* env = ic_get_env(); if (env==NULL) return NULL;
|
||||||
|
ssize_t len = ic_strlen(s);
|
||||||
|
char* p = mem_malloc_tp_n( env->mem, char, len + 1 );
|
||||||
|
if (p == NULL) return NULL;
|
||||||
|
ic_memcpy( p, s, len );
|
||||||
|
p[len] = 0;
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
|
||||||
|
//-------------------------------------------------------------
|
||||||
|
// Terminal
|
||||||
|
//-------------------------------------------------------------
|
||||||
|
|
||||||
|
ic_public void ic_term_init(void) {
|
||||||
|
ic_env_t* env = ic_get_env(); if (env==NULL) return;
|
||||||
|
if (env->term==NULL) return;
|
||||||
|
term_start_raw(env->term);
|
||||||
|
}
|
||||||
|
|
||||||
|
ic_public void ic_term_done(void) {
|
||||||
|
ic_env_t* env = ic_get_env(); if (env==NULL) return;
|
||||||
|
if (env->term==NULL) return;
|
||||||
|
term_end_raw(env->term,false);
|
||||||
|
}
|
||||||
|
|
||||||
|
ic_public void ic_term_flush(void) {
|
||||||
|
ic_env_t* env = ic_get_env(); if (env==NULL) return;
|
||||||
|
if (env->term==NULL) return;
|
||||||
|
term_flush(env->term);
|
||||||
|
}
|
||||||
|
|
||||||
|
ic_public void ic_term_write(const char* s) {
|
||||||
|
ic_env_t* env = ic_get_env(); if (env==NULL) return;
|
||||||
|
if (env->term == NULL) return;
|
||||||
|
term_write(env->term, s);
|
||||||
|
}
|
||||||
|
|
||||||
|
ic_public void ic_term_writeln(const char* s) {
|
||||||
|
ic_env_t* env = ic_get_env(); if (env==NULL) return;
|
||||||
|
if (env->term == NULL) return;
|
||||||
|
term_writeln(env->term, s);
|
||||||
|
}
|
||||||
|
|
||||||
|
ic_public void ic_term_writef(const char* fmt, ...) {
|
||||||
|
va_list ap;
|
||||||
|
va_start(ap, fmt);
|
||||||
|
ic_term_vwritef(fmt, ap);
|
||||||
|
va_end(ap);
|
||||||
|
}
|
||||||
|
|
||||||
|
ic_public void ic_term_vwritef(const char* fmt, va_list args) {
|
||||||
|
ic_env_t* env = ic_get_env(); if (env==NULL) return;
|
||||||
|
if (env->term == NULL) return;
|
||||||
|
term_vwritef(env->term, fmt, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
ic_public void ic_term_reset( void ) {
|
||||||
|
ic_env_t* env = ic_get_env(); if (env==NULL) return;
|
||||||
|
if (env->term == NULL) return;
|
||||||
|
term_attr_reset(env->term);
|
||||||
|
}
|
||||||
|
|
||||||
|
ic_public void ic_term_style( const char* style ) {
|
||||||
|
ic_env_t* env = ic_get_env(); if (env==NULL) return;
|
||||||
|
if (env->term == NULL || env->bbcode == NULL) return;
|
||||||
|
term_set_attr( env->term, bbcode_style(env->bbcode, style));
|
||||||
|
}
|
||||||
|
|
||||||
|
ic_public int ic_term_get_color_bits(void) {
|
||||||
|
ic_env_t* env = ic_get_env();
|
||||||
|
if (env==NULL || env->term==NULL) return 4;
|
||||||
|
return term_get_color_bits(env->term);
|
||||||
|
}
|
||||||
|
|
||||||
|
ic_public void ic_term_bold(bool enable) {
|
||||||
|
ic_env_t* env = ic_get_env(); if (env==NULL || env->term==NULL) return;
|
||||||
|
term_bold(env->term, enable);
|
||||||
|
}
|
||||||
|
|
||||||
|
ic_public void ic_term_underline(bool enable) {
|
||||||
|
ic_env_t* env = ic_get_env(); if (env==NULL || env->term==NULL) return;
|
||||||
|
term_underline(env->term, enable);
|
||||||
|
}
|
||||||
|
|
||||||
|
ic_public void ic_term_italic(bool enable) {
|
||||||
|
ic_env_t* env = ic_get_env(); if (env==NULL || env->term==NULL) return;
|
||||||
|
term_italic(env->term, enable);
|
||||||
|
}
|
||||||
|
|
||||||
|
ic_public void ic_term_reverse(bool enable) {
|
||||||
|
ic_env_t* env = ic_get_env(); if (env==NULL || env->term==NULL) return;
|
||||||
|
term_reverse(env->term, enable);
|
||||||
|
}
|
||||||
|
|
||||||
|
ic_public void ic_term_color_ansi(bool foreground, int ansi_color) {
|
||||||
|
ic_env_t* env = ic_get_env(); if (env==NULL || env->term==NULL) return;
|
||||||
|
ic_color_t color = color_from_ansi256(ansi_color);
|
||||||
|
if (foreground) { term_color(env->term, color); }
|
||||||
|
else { term_bgcolor(env->term, color); }
|
||||||
|
}
|
||||||
|
|
||||||
|
ic_public void ic_term_color_rgb(bool foreground, uint32_t hcolor) {
|
||||||
|
ic_env_t* env = ic_get_env(); if (env==NULL || env->term==NULL) return;
|
||||||
|
ic_color_t color = ic_rgb(hcolor);
|
||||||
|
if (foreground) { term_color(env->term, color); }
|
||||||
|
else { term_bgcolor(env->term, color); }
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//-------------------------------------------------------------
|
||||||
|
// Readline with temporary completer and highlighter
|
||||||
|
//-------------------------------------------------------------
|
||||||
|
|
||||||
|
ic_public char* ic_readline_ex(const char* prompt_text,
|
||||||
|
ic_completer_fun_t* completer, void* completer_arg,
|
||||||
|
ic_highlight_fun_t* highlighter, void* highlighter_arg )
|
||||||
|
{
|
||||||
|
ic_env_t* env = ic_get_env(); if (env == NULL) return NULL;
|
||||||
|
// save previous
|
||||||
|
ic_completer_fun_t* prev_completer;
|
||||||
|
void* prev_completer_arg;
|
||||||
|
completions_get_completer(env->completions, &prev_completer, &prev_completer_arg);
|
||||||
|
ic_highlight_fun_t* prev_highlighter = env->highlighter;
|
||||||
|
void* prev_highlighter_arg = env->highlighter_arg;
|
||||||
|
// call with current
|
||||||
|
if (completer != NULL) { ic_set_default_completer(completer, completer_arg); }
|
||||||
|
if (highlighter != NULL) { ic_set_default_highlighter(highlighter, highlighter_arg); }
|
||||||
|
char* res = ic_readline(prompt_text);
|
||||||
|
// restore previous
|
||||||
|
ic_set_default_completer(prev_completer, prev_completer_arg);
|
||||||
|
ic_set_default_highlighter(prev_highlighter, prev_highlighter_arg);
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//-------------------------------------------------------------
|
||||||
|
// Initialize
|
||||||
|
//-------------------------------------------------------------
|
||||||
|
|
||||||
|
static void ic_atexit(void);
|
||||||
|
|
||||||
|
static void ic_env_free(ic_env_t* env) {
|
||||||
|
if (env == NULL) return;
|
||||||
|
history_save(env->history);
|
||||||
|
history_free(env->history);
|
||||||
|
completions_free(env->completions);
|
||||||
|
bbcode_free(env->bbcode);
|
||||||
|
term_free(env->term);
|
||||||
|
tty_free(env->tty);
|
||||||
|
mem_free(env->mem, env->cprompt_marker);
|
||||||
|
mem_free(env->mem,env->prompt_marker);
|
||||||
|
mem_free(env->mem, env->match_braces);
|
||||||
|
mem_free(env->mem, env->auto_braces);
|
||||||
|
env->prompt_marker = NULL;
|
||||||
|
|
||||||
|
// and deallocate ourselves
|
||||||
|
alloc_t* mem = env->mem;
|
||||||
|
mem_free(mem, env);
|
||||||
|
|
||||||
|
// and finally the custom memory allocation structure
|
||||||
|
mem_free(mem, mem);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static ic_env_t* ic_env_create( ic_malloc_fun_t* _malloc, ic_realloc_fun_t* _realloc, ic_free_fun_t* _free )
|
||||||
|
{
|
||||||
|
if (_malloc == NULL) _malloc = &malloc;
|
||||||
|
if (_realloc == NULL) _realloc = &realloc;
|
||||||
|
if (_free == NULL) _free = &free;
|
||||||
|
// allocate
|
||||||
|
alloc_t* mem = (alloc_t*)_malloc(sizeof(alloc_t));
|
||||||
|
if (mem == NULL) return NULL;
|
||||||
|
mem->malloc = _malloc;
|
||||||
|
mem->realloc = _realloc;
|
||||||
|
mem->free = _free;
|
||||||
|
ic_env_t* env = mem_zalloc_tp(mem, ic_env_t);
|
||||||
|
if (env==NULL) {
|
||||||
|
mem->free(mem);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
env->mem = mem;
|
||||||
|
|
||||||
|
// Initialize
|
||||||
|
env->tty = tty_new(env->mem, -1); // can return NULL
|
||||||
|
env->term = term_new(env->mem, env->tty, false, false, -1 );
|
||||||
|
env->history = history_new(env->mem);
|
||||||
|
env->completions = completions_new(env->mem);
|
||||||
|
env->bbcode = bbcode_new(env->mem, env->term);
|
||||||
|
env->hint_delay = 400;
|
||||||
|
|
||||||
|
if (env->tty == NULL || env->term==NULL ||
|
||||||
|
env->completions == NULL || env->history == NULL || env->bbcode == NULL ||
|
||||||
|
!term_is_interactive(env->term))
|
||||||
|
{
|
||||||
|
env->noedit = true;
|
||||||
|
}
|
||||||
|
env->multiline_eol = '\\';
|
||||||
|
|
||||||
|
bbcode_style_def(env->bbcode, "ic-prompt", "ansi-green" );
|
||||||
|
bbcode_style_def(env->bbcode, "ic-info", "ansi-darkgray" );
|
||||||
|
bbcode_style_def(env->bbcode, "ic-diminish", "ansi-lightgray" );
|
||||||
|
bbcode_style_def(env->bbcode, "ic-emphasis", "#ffffd7" );
|
||||||
|
bbcode_style_def(env->bbcode, "ic-hint", "ansi-darkgray" );
|
||||||
|
bbcode_style_def(env->bbcode, "ic-error", "#d70000" );
|
||||||
|
bbcode_style_def(env->bbcode, "ic-bracematch","ansi-white"); // color = #F7DC6F" );
|
||||||
|
|
||||||
|
bbcode_style_def(env->bbcode, "keyword", "#569cd6" );
|
||||||
|
bbcode_style_def(env->bbcode, "control", "#c586c0" );
|
||||||
|
bbcode_style_def(env->bbcode, "number", "#b5cea8" );
|
||||||
|
bbcode_style_def(env->bbcode, "string", "#ce9178" );
|
||||||
|
bbcode_style_def(env->bbcode, "comment", "#6A9955" );
|
||||||
|
bbcode_style_def(env->bbcode, "type", "darkcyan" );
|
||||||
|
bbcode_style_def(env->bbcode, "constant", "#569cd6" );
|
||||||
|
|
||||||
|
set_prompt_marker(env, NULL, NULL);
|
||||||
|
return env;
|
||||||
|
}
|
||||||
|
|
||||||
|
static ic_env_t* rpenv;
|
||||||
|
|
||||||
|
static void ic_atexit(void) {
|
||||||
|
if (rpenv != NULL) {
|
||||||
|
ic_env_free(rpenv);
|
||||||
|
rpenv = NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ic_private ic_env_t* ic_get_env(void) {
|
||||||
|
if (rpenv==NULL) {
|
||||||
|
rpenv = ic_env_create( NULL, NULL, NULL );
|
||||||
|
if (rpenv != NULL) { atexit( &ic_atexit ); }
|
||||||
|
}
|
||||||
|
return rpenv;
|
||||||
|
}
|
||||||
|
|
||||||
|
ic_public void ic_init_custom_malloc( ic_malloc_fun_t* _malloc, ic_realloc_fun_t* _realloc, ic_free_fun_t* _free ) {
|
||||||
|
assert(rpenv == NULL);
|
||||||
|
if (rpenv != NULL) {
|
||||||
|
ic_env_free(rpenv);
|
||||||
|
rpenv = ic_env_create( _malloc, _realloc, _free );
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
rpenv = ic_env_create( _malloc, _realloc, _free );
|
||||||
|
if (rpenv != NULL) {
|
||||||
|
atexit( &ic_atexit );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
1038
deps/isocline/src/stringbuf.c
vendored
Normal file
1038
deps/isocline/src/stringbuf.c
vendored
Normal file
File diff suppressed because it is too large
Load Diff
121
deps/isocline/src/stringbuf.h
vendored
Normal file
121
deps/isocline/src/stringbuf.h
vendored
Normal file
@ -0,0 +1,121 @@
|
|||||||
|
/* ----------------------------------------------------------------------------
|
||||||
|
Copyright (c) 2021, Daan Leijen
|
||||||
|
This is free software; you can redistribute it and/or modify it
|
||||||
|
under the terms of the MIT License. A copy of the license can be
|
||||||
|
found in the "LICENSE" file at the root of this distribution.
|
||||||
|
-----------------------------------------------------------------------------*/
|
||||||
|
#pragma once
|
||||||
|
#ifndef IC_STRINGBUF_H
|
||||||
|
#define IC_STRINGBUF_H
|
||||||
|
|
||||||
|
#include <stdarg.h>
|
||||||
|
#include "common.h"
|
||||||
|
|
||||||
|
//-------------------------------------------------------------
|
||||||
|
// string buffer
|
||||||
|
// in-place modified buffer with edit operations
|
||||||
|
// that grows on demand.
|
||||||
|
//-------------------------------------------------------------
|
||||||
|
|
||||||
|
// abstract string buffer
|
||||||
|
struct stringbuf_s;
|
||||||
|
typedef struct stringbuf_s stringbuf_t;
|
||||||
|
|
||||||
|
ic_private stringbuf_t* sbuf_new( alloc_t* mem );
|
||||||
|
ic_private void sbuf_free( stringbuf_t* sbuf );
|
||||||
|
ic_private char* sbuf_free_dup(stringbuf_t* sbuf);
|
||||||
|
ic_private ssize_t sbuf_len(const stringbuf_t* s);
|
||||||
|
|
||||||
|
ic_private const char* sbuf_string_at( stringbuf_t* sbuf, ssize_t pos );
|
||||||
|
ic_private const char* sbuf_string( stringbuf_t* sbuf );
|
||||||
|
ic_private char sbuf_char_at(stringbuf_t* sbuf, ssize_t pos);
|
||||||
|
ic_private char* sbuf_strdup_at( stringbuf_t* sbuf, ssize_t pos );
|
||||||
|
ic_private char* sbuf_strdup( stringbuf_t* sbuf );
|
||||||
|
ic_private char* sbuf_strdup_from_utf8(stringbuf_t* sbuf); // decode to locale
|
||||||
|
|
||||||
|
|
||||||
|
ic_private ssize_t sbuf_appendf(stringbuf_t* sb, const char* fmt, ...);
|
||||||
|
ic_private ssize_t sbuf_append_vprintf(stringbuf_t* sb, const char* fmt, va_list args);
|
||||||
|
|
||||||
|
ic_private stringbuf_t* sbuf_split_at( stringbuf_t* sb, ssize_t pos );
|
||||||
|
|
||||||
|
// primitive edit operations (inserts return the new position)
|
||||||
|
ic_private void sbuf_clear(stringbuf_t* sbuf);
|
||||||
|
ic_private void sbuf_replace(stringbuf_t* sbuf, const char* s);
|
||||||
|
ic_private void sbuf_delete_at(stringbuf_t* sbuf, ssize_t pos, ssize_t count);
|
||||||
|
ic_private void sbuf_delete_from_to(stringbuf_t* sbuf, ssize_t pos, ssize_t end);
|
||||||
|
ic_private void sbuf_delete_from(stringbuf_t* sbuf, ssize_t pos );
|
||||||
|
ic_private ssize_t sbuf_insert_at_n(stringbuf_t* sbuf, const char* s, ssize_t n, ssize_t pos );
|
||||||
|
ic_private ssize_t sbuf_insert_at(stringbuf_t* sbuf, const char* s, ssize_t pos );
|
||||||
|
ic_private ssize_t sbuf_insert_char_at(stringbuf_t* sbuf, char c, ssize_t pos );
|
||||||
|
ic_private ssize_t sbuf_insert_unicode_at(stringbuf_t* sbuf, unicode_t u, ssize_t pos);
|
||||||
|
ic_private ssize_t sbuf_append_n(stringbuf_t* sbuf, const char* s, ssize_t n);
|
||||||
|
ic_private ssize_t sbuf_append(stringbuf_t* sbuf, const char* s);
|
||||||
|
ic_private ssize_t sbuf_append_char(stringbuf_t* sbuf, char c);
|
||||||
|
|
||||||
|
// high level edit operations (return the new position)
|
||||||
|
ic_private ssize_t sbuf_next( stringbuf_t* sbuf, ssize_t pos, ssize_t* cwidth );
|
||||||
|
ic_private ssize_t sbuf_prev( stringbuf_t* sbuf, ssize_t pos, ssize_t* cwidth );
|
||||||
|
ic_private ssize_t sbuf_next_ofs(stringbuf_t* sbuf, ssize_t pos, ssize_t* cwidth);
|
||||||
|
|
||||||
|
ic_private ssize_t sbuf_delete_char_before( stringbuf_t* sbuf, ssize_t pos );
|
||||||
|
ic_private void sbuf_delete_char_at( stringbuf_t* sbuf, ssize_t pos );
|
||||||
|
ic_private ssize_t sbuf_swap_char( stringbuf_t* sbuf, ssize_t pos );
|
||||||
|
|
||||||
|
ic_private ssize_t sbuf_find_line_start( stringbuf_t* sbuf, ssize_t pos );
|
||||||
|
ic_private ssize_t sbuf_find_line_end( stringbuf_t* sbuf, ssize_t pos );
|
||||||
|
ic_private ssize_t sbuf_find_word_start( stringbuf_t* sbuf, ssize_t pos );
|
||||||
|
ic_private ssize_t sbuf_find_word_end( stringbuf_t* sbuf, ssize_t pos );
|
||||||
|
ic_private ssize_t sbuf_find_ws_word_start( stringbuf_t* sbuf, ssize_t pos );
|
||||||
|
ic_private ssize_t sbuf_find_ws_word_end( stringbuf_t* sbuf, ssize_t pos );
|
||||||
|
|
||||||
|
// parse a decimal
|
||||||
|
ic_private bool ic_atoz(const char* s, ssize_t* i);
|
||||||
|
// parse two decimals separated by a semicolon
|
||||||
|
ic_private bool ic_atoz2(const char* s, ssize_t* i, ssize_t* j);
|
||||||
|
ic_private bool ic_atou32(const char* s, uint32_t* pu);
|
||||||
|
|
||||||
|
// row/column info
|
||||||
|
typedef struct rowcol_s {
|
||||||
|
ssize_t row;
|
||||||
|
ssize_t col;
|
||||||
|
ssize_t row_start;
|
||||||
|
ssize_t row_len;
|
||||||
|
bool first_on_row;
|
||||||
|
bool last_on_row;
|
||||||
|
} rowcol_t;
|
||||||
|
|
||||||
|
// find row/col position
|
||||||
|
ic_private ssize_t sbuf_get_pos_at_rc( stringbuf_t* sbuf, ssize_t termw, ssize_t promptw, ssize_t cpromptw,
|
||||||
|
ssize_t row, ssize_t col );
|
||||||
|
// get row/col for a given position
|
||||||
|
ic_private ssize_t sbuf_get_rc_at_pos( stringbuf_t* sbuf, ssize_t termw, ssize_t promptw, ssize_t cpromptw,
|
||||||
|
ssize_t pos, rowcol_t* rc );
|
||||||
|
|
||||||
|
ic_private ssize_t sbuf_get_wrapped_rc_at_pos( stringbuf_t* sbuf, ssize_t termw, ssize_t newtermw, ssize_t promptw, ssize_t cpromptw,
|
||||||
|
ssize_t pos, rowcol_t* rc );
|
||||||
|
|
||||||
|
// row iteration
|
||||||
|
typedef bool (row_fun_t)(const char* s,
|
||||||
|
ssize_t row, ssize_t row_start, ssize_t row_len,
|
||||||
|
ssize_t startw, // prompt width
|
||||||
|
bool is_wrap, const void* arg, void* res);
|
||||||
|
|
||||||
|
ic_private ssize_t sbuf_for_each_row( stringbuf_t* sbuf, ssize_t termw, ssize_t promptw, ssize_t cpromptw,
|
||||||
|
row_fun_t* fun, void* arg, void* res );
|
||||||
|
|
||||||
|
|
||||||
|
//-------------------------------------------------------------
|
||||||
|
// Strings
|
||||||
|
//-------------------------------------------------------------
|
||||||
|
|
||||||
|
// skip a single CSI sequence (ESC [ ...)
|
||||||
|
ic_private bool skip_csi_esc( const char* s, ssize_t len, ssize_t* esclen ); // used in term.c
|
||||||
|
|
||||||
|
ic_private ssize_t str_column_width( const char* s );
|
||||||
|
ic_private ssize_t str_prev_ofs( const char* s, ssize_t pos, ssize_t* cwidth );
|
||||||
|
ic_private ssize_t str_next_ofs( const char* s, ssize_t len, ssize_t pos, ssize_t* cwidth );
|
||||||
|
ic_private ssize_t str_skip_until_fit( const char* s, ssize_t max_width); // tail that fits
|
||||||
|
ic_private ssize_t str_take_while_fit( const char* s, ssize_t max_width); // prefix that fits
|
||||||
|
|
||||||
|
#endif // IC_STRINGBUF_H
|
1124
deps/isocline/src/term.c
vendored
Normal file
1124
deps/isocline/src/term.c
vendored
Normal file
File diff suppressed because it is too large
Load Diff
85
deps/isocline/src/term.h
vendored
Normal file
85
deps/isocline/src/term.h
vendored
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
/* ----------------------------------------------------------------------------
|
||||||
|
Copyright (c) 2021, Daan Leijen
|
||||||
|
This is free software; you can redistribute it and/or modify it
|
||||||
|
under the terms of the MIT License. A copy of the license can be
|
||||||
|
found in the "LICENSE" file at the root of this distribution.
|
||||||
|
-----------------------------------------------------------------------------*/
|
||||||
|
#pragma once
|
||||||
|
#ifndef IC_TERM_H
|
||||||
|
#define IC_TERM_H
|
||||||
|
|
||||||
|
#include "common.h"
|
||||||
|
#include "tty.h"
|
||||||
|
#include "stringbuf.h"
|
||||||
|
#include "attr.h"
|
||||||
|
|
||||||
|
struct term_s;
|
||||||
|
typedef struct term_s term_t;
|
||||||
|
|
||||||
|
typedef enum buffer_mode_e {
|
||||||
|
UNBUFFERED,
|
||||||
|
LINEBUFFERED,
|
||||||
|
BUFFERED,
|
||||||
|
} buffer_mode_t;
|
||||||
|
|
||||||
|
// Primitives
|
||||||
|
ic_private term_t* term_new(alloc_t* mem, tty_t* tty, bool nocolor, bool silent, int fd_out);
|
||||||
|
ic_private void term_free(term_t* term);
|
||||||
|
|
||||||
|
ic_private bool term_is_interactive(const term_t* term);
|
||||||
|
ic_private void term_start_raw(term_t* term);
|
||||||
|
ic_private void term_end_raw(term_t* term, bool force);
|
||||||
|
|
||||||
|
ic_private bool term_enable_beep(term_t* term, bool enable);
|
||||||
|
ic_private bool term_enable_color(term_t* term, bool enable);
|
||||||
|
|
||||||
|
ic_private void term_flush(term_t* term);
|
||||||
|
ic_private buffer_mode_t term_set_buffer_mode(term_t* term, buffer_mode_t mode);
|
||||||
|
|
||||||
|
ic_private void term_write_n(term_t* term, const char* s, ssize_t n);
|
||||||
|
ic_private void term_write(term_t* term, const char* s);
|
||||||
|
ic_private void term_writeln(term_t* term, const char* s);
|
||||||
|
ic_private void term_write_char(term_t* term, char c);
|
||||||
|
|
||||||
|
ic_private void term_write_repeat(term_t* term, const char* s, ssize_t count );
|
||||||
|
ic_private void term_beep(term_t* term);
|
||||||
|
|
||||||
|
ic_private bool term_update_dim(term_t* term);
|
||||||
|
|
||||||
|
ic_private ssize_t term_get_width(term_t* term);
|
||||||
|
ic_private ssize_t term_get_height(term_t* term);
|
||||||
|
ic_private int term_get_color_bits(term_t* term);
|
||||||
|
|
||||||
|
// Helpers
|
||||||
|
ic_private void term_writef(term_t* term, const char* fmt, ...);
|
||||||
|
ic_private void term_vwritef(term_t* term, const char* fmt, va_list args);
|
||||||
|
|
||||||
|
ic_private void term_left(term_t* term, ssize_t n);
|
||||||
|
ic_private void term_right(term_t* term, ssize_t n);
|
||||||
|
ic_private void term_up(term_t* term, ssize_t n);
|
||||||
|
ic_private void term_down(term_t* term, ssize_t n);
|
||||||
|
ic_private void term_start_of_line(term_t* term );
|
||||||
|
ic_private void term_clear_line(term_t* term);
|
||||||
|
ic_private void term_clear_to_end_of_line(term_t* term);
|
||||||
|
// ic_private void term_clear_lines_to_end(term_t* term);
|
||||||
|
|
||||||
|
|
||||||
|
ic_private void term_attr_reset(term_t* term);
|
||||||
|
ic_private void term_underline(term_t* term, bool on);
|
||||||
|
ic_private void term_reverse(term_t* term, bool on);
|
||||||
|
ic_private void term_bold(term_t* term, bool on);
|
||||||
|
ic_private void term_italic(term_t* term, bool on);
|
||||||
|
|
||||||
|
ic_private void term_color(term_t* term, ic_color_t color);
|
||||||
|
ic_private void term_bgcolor(term_t* term, ic_color_t color);
|
||||||
|
|
||||||
|
// Formatted output
|
||||||
|
|
||||||
|
ic_private attr_t term_get_attr( const term_t* term );
|
||||||
|
ic_private void term_set_attr( term_t* term, attr_t attr );
|
||||||
|
ic_private void term_write_formatted( term_t* term, const char* s, const attr_t* attrs );
|
||||||
|
ic_private void term_write_formatted_n( term_t* term, const char* s, const attr_t* attrs, ssize_t n );
|
||||||
|
|
||||||
|
ic_private ic_color_t color_from_ansi256(ssize_t i);
|
||||||
|
|
||||||
|
#endif // IC_TERM_H
|
371
deps/isocline/src/term_color.c
vendored
Normal file
371
deps/isocline/src/term_color.c
vendored
Normal file
@ -0,0 +1,371 @@
|
|||||||
|
/* ----------------------------------------------------------------------------
|
||||||
|
Copyright (c) 2021, Daan Leijen
|
||||||
|
This is free software; you can redistribute it and/or modify it
|
||||||
|
under the terms of the MIT License. A copy of the license can be
|
||||||
|
found in the "LICENSE" file at the root of this distribution.
|
||||||
|
-----------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
// This file is included in "term.c"
|
||||||
|
|
||||||
|
//-------------------------------------------------------------
|
||||||
|
// Standard ANSI palette for 256 colors
|
||||||
|
//-------------------------------------------------------------
|
||||||
|
|
||||||
|
static uint32_t ansi256[256] = {
|
||||||
|
// not const as on some platforms (e.g. Windows, xterm) we update the first 16 entries with the actual used colors.
|
||||||
|
// 0, standard ANSI
|
||||||
|
0x000000, 0x800000, 0x008000, 0x808000, 0x000080, 0x800080,
|
||||||
|
0x008080, 0xc0c0c0,
|
||||||
|
// 8, bright ANSI
|
||||||
|
0x808080, 0xff0000, 0x00ff00, 0xffff00, 0x0000ff, 0xff00ff,
|
||||||
|
0x00ffff, 0xffffff,
|
||||||
|
// 6x6x6 RGB colors
|
||||||
|
// 16
|
||||||
|
0x000000, 0x00005f, 0x000087, 0x0000af, 0x0000d7, 0x0000ff,
|
||||||
|
0x005f00, 0x005f5f, 0x005f87, 0x005faf, 0x005fd7, 0x005fff,
|
||||||
|
0x008700, 0x00875f, 0x008787, 0x0087af, 0x0087d7, 0x0087ff,
|
||||||
|
0x00af00, 0x00af5f, 0x00af87, 0x00afaf, 0x00afd7, 0x00afff,
|
||||||
|
0x00d700, 0x00d75f, 0x00d787, 0x00d7af, 0x00d7d7, 0x00d7ff,
|
||||||
|
0x00ff00, 0x00ff5f, 0x00ff87, 0x00ffaf, 0x00ffd7, 0x00ffff,
|
||||||
|
// 52
|
||||||
|
0x5f0000, 0x5f005f, 0x5f0087, 0x5f00af, 0x5f00d7, 0x5f00ff,
|
||||||
|
0x5f5f00, 0x5f5f5f, 0x5f5f87, 0x5f5faf, 0x5f5fd7, 0x5f5fff,
|
||||||
|
0x5f8700, 0x5f875f, 0x5f8787, 0x5f87af, 0x5f87d7, 0x5f87ff,
|
||||||
|
0x5faf00, 0x5faf5f, 0x5faf87, 0x5fafaf, 0x5fafd7, 0x5fafff,
|
||||||
|
0x5fd700, 0x5fd75f, 0x5fd787, 0x5fd7af, 0x5fd7d7, 0x5fd7ff,
|
||||||
|
0x5fff00, 0x5fff5f, 0x5fff87, 0x5fffaf, 0x5fffd7, 0x5fffff,
|
||||||
|
// 88
|
||||||
|
0x870000, 0x87005f, 0x870087, 0x8700af, 0x8700d7, 0x8700ff,
|
||||||
|
0x875f00, 0x875f5f, 0x875f87, 0x875faf, 0x875fd7, 0x875fff,
|
||||||
|
0x878700, 0x87875f, 0x878787, 0x8787af, 0x8787d7, 0x8787ff,
|
||||||
|
0x87af00, 0x87af5f, 0x87af87, 0x87afaf, 0x87afd7, 0x87afff,
|
||||||
|
0x87d700, 0x87d75f, 0x87d787, 0x87d7af, 0x87d7d7, 0x87d7ff,
|
||||||
|
0x87ff00, 0x87ff5f, 0x87ff87, 0x87ffaf, 0x87ffd7, 0x87ffff,
|
||||||
|
// 124
|
||||||
|
0xaf0000, 0xaf005f, 0xaf0087, 0xaf00af, 0xaf00d7, 0xaf00ff,
|
||||||
|
0xaf5f00, 0xaf5f5f, 0xaf5f87, 0xaf5faf, 0xaf5fd7, 0xaf5fff,
|
||||||
|
0xaf8700, 0xaf875f, 0xaf8787, 0xaf87af, 0xaf87d7, 0xaf87ff,
|
||||||
|
0xafaf00, 0xafaf5f, 0xafaf87, 0xafafaf, 0xafafd7, 0xafafff,
|
||||||
|
0xafd700, 0xafd75f, 0xafd787, 0xafd7af, 0xafd7d7, 0xafd7ff,
|
||||||
|
0xafff00, 0xafff5f, 0xafff87, 0xafffaf, 0xafffd7, 0xafffff,
|
||||||
|
// 160
|
||||||
|
0xd70000, 0xd7005f, 0xd70087, 0xd700af, 0xd700d7, 0xd700ff,
|
||||||
|
0xd75f00, 0xd75f5f, 0xd75f87, 0xd75faf, 0xd75fd7, 0xd75fff,
|
||||||
|
0xd78700, 0xd7875f, 0xd78787, 0xd787af, 0xd787d7, 0xd787ff,
|
||||||
|
0xd7af00, 0xd7af5f, 0xd7af87, 0xd7afaf, 0xd7afd7, 0xd7afff,
|
||||||
|
0xd7d700, 0xd7d75f, 0xd7d787, 0xd7d7af, 0xd7d7d7, 0xd7d7ff,
|
||||||
|
0xd7ff00, 0xd7ff5f, 0xd7ff87, 0xd7ffaf, 0xd7ffd7, 0xd7ffff,
|
||||||
|
// 196
|
||||||
|
0xff0000, 0xff005f, 0xff0087, 0xff00af, 0xff00d7, 0xff00ff,
|
||||||
|
0xff5f00, 0xff5f5f, 0xff5f87, 0xff5faf, 0xff5fd7, 0xff5fff,
|
||||||
|
0xff8700, 0xff875f, 0xff8787, 0xff87af, 0xff87d7, 0xff87ff,
|
||||||
|
0xffaf00, 0xffaf5f, 0xffaf87, 0xffafaf, 0xffafd7, 0xffafff,
|
||||||
|
0xffd700, 0xffd75f, 0xffd787, 0xffd7af, 0xffd7d7, 0xffd7ff,
|
||||||
|
0xffff00, 0xffff5f, 0xffff87, 0xffffaf, 0xffffd7, 0xffffff,
|
||||||
|
// 232, gray scale
|
||||||
|
0x080808, 0x121212, 0x1c1c1c, 0x262626, 0x303030, 0x3a3a3a,
|
||||||
|
0x444444, 0x4e4e4e, 0x585858, 0x626262, 0x6c6c6c, 0x767676,
|
||||||
|
0x808080, 0x8a8a8a, 0x949494, 0x9e9e9e, 0xa8a8a8, 0xb2b2b2,
|
||||||
|
0xbcbcbc, 0xc6c6c6, 0xd0d0d0, 0xdadada, 0xe4e4e4, 0xeeeeee
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
//-------------------------------------------------------------
|
||||||
|
// Create colors
|
||||||
|
//-------------------------------------------------------------
|
||||||
|
|
||||||
|
// Create a color from a 24-bit color value.
|
||||||
|
ic_private ic_color_t ic_rgb(uint32_t hex) {
|
||||||
|
return (ic_color_t)(0x1000000 | (hex & 0xFFFFFF));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Limit an int to values between 0 and 255.
|
||||||
|
static uint32_t ic_cap8(ssize_t i) {
|
||||||
|
return (i < 0 ? 0 : (i > 255 ? 255 : (uint32_t)i));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a color from a 24-bit color value.
|
||||||
|
ic_private ic_color_t ic_rgbx(ssize_t r, ssize_t g, ssize_t b) {
|
||||||
|
return ic_rgb( (ic_cap8(r)<<16) | (ic_cap8(g)<<8) | ic_cap8(b) );
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//-------------------------------------------------------------
|
||||||
|
// Match an rgb color to a ansi8, ansi16, or ansi256
|
||||||
|
//-------------------------------------------------------------
|
||||||
|
|
||||||
|
static bool color_is_rgb( ic_color_t color ) {
|
||||||
|
return (color >= IC_RGB(0)); // bit 24 is set for rgb colors
|
||||||
|
}
|
||||||
|
|
||||||
|
static void color_to_rgb(ic_color_t color, int* r, int* g, int* b) {
|
||||||
|
assert(color_is_rgb(color));
|
||||||
|
*r = ((color >> 16) & 0xFF);
|
||||||
|
*g = ((color >> 8) & 0xFF);
|
||||||
|
*b = (color & 0xFF);
|
||||||
|
}
|
||||||
|
|
||||||
|
ic_private ic_color_t color_from_ansi256(ssize_t i) {
|
||||||
|
if (i >= 0 && i < 8) {
|
||||||
|
return (IC_ANSI_BLACK + (uint32_t)i);
|
||||||
|
}
|
||||||
|
else if (i >= 8 && i < 16) {
|
||||||
|
return (IC_ANSI_DARKGRAY + (uint32_t)(i - 8));
|
||||||
|
}
|
||||||
|
else if (i >= 16 && i <= 255) {
|
||||||
|
return ic_rgb( ansi256[i] );
|
||||||
|
}
|
||||||
|
else if (i == 256) {
|
||||||
|
return IC_ANSI_DEFAULT;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return IC_ANSI_DEFAULT;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool is_grayish(int r, int g, int b) {
|
||||||
|
return (abs(r-g) <= 4) && (abs((r+g)/2 - b) <= 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool is_grayish_color( uint32_t rgb ) {
|
||||||
|
int r, g, b;
|
||||||
|
color_to_rgb(IC_RGB(rgb),&r,&g,&b);
|
||||||
|
return is_grayish(r,g,b);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int_least32_t sqr(int_least32_t x) {
|
||||||
|
return x*x;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Approximation to delta-E CIE color distance using much
|
||||||
|
// simpler calculations. See <https://www.compuphase.com/cmetric.htm>.
|
||||||
|
// This is essentialy weighted euclidean distance but the weight distribution
|
||||||
|
// depends on how big the "red" component of the color is.
|
||||||
|
// We do not take the square root as we only need to find
|
||||||
|
// the minimal distance (and multiply by 256 to increase precision).
|
||||||
|
// Needs at least 28-bit signed integers to avoid overflow.
|
||||||
|
static int_least32_t rgb_distance_rmean( uint32_t color, int r2, int g2, int b2 ) {
|
||||||
|
int r1, g1, b1;
|
||||||
|
color_to_rgb(IC_RGB(color),&r1,&g1,&b1);
|
||||||
|
int_least32_t rmean = (r1 + r2) / 2;
|
||||||
|
int_least32_t dr2 = sqr(r1 - r2);
|
||||||
|
int_least32_t dg2 = sqr(g1 - g2);
|
||||||
|
int_least32_t db2 = sqr(b1 - b2);
|
||||||
|
int_least32_t dist = ((512+rmean)*dr2) + 1024*dg2 + ((767-rmean)*db2);
|
||||||
|
return dist;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Another approximation to delta-E CIE color distance using
|
||||||
|
// simpler calculations. Similar to `rmean` but adds an adjustment factor
|
||||||
|
// based on the "red/blue" difference.
|
||||||
|
static int_least32_t rgb_distance_rbmean( uint32_t color, int r2, int g2, int b2 ) {
|
||||||
|
int r1, g1, b1;
|
||||||
|
color_to_rgb(IC_RGB(color),&r1,&g1,&b1);
|
||||||
|
int_least32_t rmean = (r1 + r2) / 2;
|
||||||
|
int_least32_t dr2 = sqr(r1 - r2);
|
||||||
|
int_least32_t dg2 = sqr(g1 - g2);
|
||||||
|
int_least32_t db2 = sqr(b1 - b2);
|
||||||
|
int_least32_t dist = 2*dr2 + 4*dg2 + 3*db2 + ((rmean*(dr2 - db2))/256);
|
||||||
|
return dist;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Maintain a small cache of recently used colors. Should be short enough to be effectively constant time.
|
||||||
|
// If we ever use a more expensive color distance method, we may increase the size a bit (64?)
|
||||||
|
// (Initial zero initialized cache is valid.)
|
||||||
|
#define RGB_CACHE_LEN (16)
|
||||||
|
typedef struct rgb_cache_s {
|
||||||
|
int last;
|
||||||
|
int indices[RGB_CACHE_LEN];
|
||||||
|
ic_color_t colors[RGB_CACHE_LEN];
|
||||||
|
} rgb_cache_t;
|
||||||
|
|
||||||
|
// remember a color in the LRU cache
|
||||||
|
void rgb_remember( rgb_cache_t* cache, ic_color_t color, int idx ) {
|
||||||
|
if (cache == NULL) return;
|
||||||
|
cache->colors[cache->last] = color;
|
||||||
|
cache->indices[cache->last] = idx;
|
||||||
|
cache->last++;
|
||||||
|
if (cache->last >= RGB_CACHE_LEN) { cache->last = 0; }
|
||||||
|
}
|
||||||
|
|
||||||
|
// quick lookup in cache; -1 on failure
|
||||||
|
int rgb_lookup( const rgb_cache_t* cache, ic_color_t color ) {
|
||||||
|
if (cache != NULL) {
|
||||||
|
for(int i = 0; i < RGB_CACHE_LEN; i++) {
|
||||||
|
if (cache->colors[i] == color) return cache->indices[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// return the index of the closest matching color
|
||||||
|
static int rgb_match( uint32_t* palette, int start, int len, rgb_cache_t* cache, ic_color_t color ) {
|
||||||
|
assert(color_is_rgb(color));
|
||||||
|
// in cache?
|
||||||
|
int min = rgb_lookup(cache,color);
|
||||||
|
if (min >= 0) {
|
||||||
|
return min;
|
||||||
|
}
|
||||||
|
// otherwise find closest color match in the palette
|
||||||
|
int r, g, b;
|
||||||
|
color_to_rgb(color,&r,&g,&b);
|
||||||
|
min = start;
|
||||||
|
int_least32_t mindist = (INT_LEAST32_MAX)/4;
|
||||||
|
for(int i = start; i < len; i++) {
|
||||||
|
//int_least32_t dist = rgb_distance_rbmean(palette[i],r,g,b);
|
||||||
|
int_least32_t dist = rgb_distance_rmean(palette[i],r,g,b);
|
||||||
|
if (is_grayish_color(palette[i]) != is_grayish(r, g, b)) {
|
||||||
|
// with few colors, make it less eager to substitute a gray for a non-gray (or the other way around)
|
||||||
|
if (len <= 16) {
|
||||||
|
dist *= 4;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
dist = (dist/4)*5;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (dist < mindist) {
|
||||||
|
min = i;
|
||||||
|
mindist = dist;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
rgb_remember(cache,color,min);
|
||||||
|
return min;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Match RGB to an index in the ANSI 256 color table
|
||||||
|
static int rgb_to_ansi256(ic_color_t color) {
|
||||||
|
static rgb_cache_t ansi256_cache;
|
||||||
|
int c = rgb_match(ansi256, 16, 256, &ansi256_cache, color); // not the first 16 ANSI colors as those may be different
|
||||||
|
//debug_msg("term: rgb %x -> ansi 256: %d\n", color, c );
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Match RGB to an ANSI 16 color code (30-37, 90-97)
|
||||||
|
static int color_to_ansi16(ic_color_t color) {
|
||||||
|
if (!color_is_rgb(color)) {
|
||||||
|
return (int)color;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
static rgb_cache_t ansi16_cache;
|
||||||
|
int c = rgb_match(ansi256, 0, 16, &ansi16_cache, color);
|
||||||
|
//debug_msg("term: rgb %x -> ansi 16: %d\n", color, c );
|
||||||
|
return (c < 8 ? 30 + c : 90 + c - 8);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Match RGB to an ANSI 16 color code (30-37, 90-97)
|
||||||
|
// but assuming the bright colors are simulated using 'bold'.
|
||||||
|
static int color_to_ansi8(ic_color_t color) {
|
||||||
|
if (!color_is_rgb(color)) {
|
||||||
|
return (int)color;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// match to basic 8 colors first
|
||||||
|
static rgb_cache_t ansi8_cache;
|
||||||
|
int c = 30 + rgb_match(ansi256, 0, 8, &ansi8_cache, color);
|
||||||
|
// and then adjust for brightness
|
||||||
|
int r, g, b;
|
||||||
|
color_to_rgb(color,&r,&g,&b);
|
||||||
|
if (r>=196 || g>=196 || b>=196) c += 60;
|
||||||
|
//debug_msg("term: rgb %x -> ansi 8: %d\n", color, c );
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//-------------------------------------------------------------
|
||||||
|
// Emit color escape codes based on the terminal capability
|
||||||
|
//-------------------------------------------------------------
|
||||||
|
|
||||||
|
static void fmt_color_ansi8( char* buf, ssize_t len, ic_color_t color, bool bg ) {
|
||||||
|
int c = color_to_ansi8(color) + (bg ? 10 : 0);
|
||||||
|
if (c >= 90) {
|
||||||
|
snprintf(buf, to_size_t(len), IC_CSI "1;%dm", c - 60);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
snprintf(buf, to_size_t(len), IC_CSI "22;%dm", c );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void fmt_color_ansi16( char* buf, ssize_t len, ic_color_t color, bool bg ) {
|
||||||
|
snprintf( buf, to_size_t(len), IC_CSI "%dm", color_to_ansi16(color) + (bg ? 10 : 0) );
|
||||||
|
}
|
||||||
|
|
||||||
|
static void fmt_color_ansi256( char* buf, ssize_t len, ic_color_t color, bool bg ) {
|
||||||
|
if (!color_is_rgb(color)) {
|
||||||
|
fmt_color_ansi16(buf,len,color,bg);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
snprintf( buf, to_size_t(len), IC_CSI "%d;5;%dm", (bg ? 48 : 38), rgb_to_ansi256(color) );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void fmt_color_rgb( char* buf, ssize_t len, ic_color_t color, bool bg ) {
|
||||||
|
if (!color_is_rgb(color)) {
|
||||||
|
fmt_color_ansi16(buf,len,color,bg);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
int r,g,b;
|
||||||
|
color_to_rgb(color, &r,&g,&b);
|
||||||
|
snprintf( buf, to_size_t(len), IC_CSI "%d;2;%d;%d;%dm", (bg ? 48 : 38), r, g, b );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void fmt_color_ex(char* buf, ssize_t len, palette_t palette, ic_color_t color, bool bg) {
|
||||||
|
if (color == IC_COLOR_NONE || palette == MONOCHROME) return;
|
||||||
|
if (palette == ANSI8) {
|
||||||
|
fmt_color_ansi8(buf,len,color,bg);
|
||||||
|
}
|
||||||
|
else if (!color_is_rgb(color) || palette == ANSI16) {
|
||||||
|
fmt_color_ansi16(buf,len,color,bg);
|
||||||
|
}
|
||||||
|
else if (palette == ANSI256) {
|
||||||
|
fmt_color_ansi256(buf,len,color,bg);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
fmt_color_rgb(buf,len,color,bg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void term_color_ex(term_t* term, ic_color_t color, bool bg) {
|
||||||
|
char buf[128+1];
|
||||||
|
fmt_color_ex(buf,128,term->palette,color,bg);
|
||||||
|
term_write(term,buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
//-------------------------------------------------------------
|
||||||
|
// Main API functions
|
||||||
|
//-------------------------------------------------------------
|
||||||
|
|
||||||
|
ic_private void term_color(term_t* term, ic_color_t color) {
|
||||||
|
term_color_ex(term,color,false);
|
||||||
|
}
|
||||||
|
|
||||||
|
ic_private void term_bgcolor(term_t* term, ic_color_t color) {
|
||||||
|
term_color_ex(term,color,true);
|
||||||
|
}
|
||||||
|
|
||||||
|
ic_private void term_append_color(term_t* term, stringbuf_t* sbuf, ic_color_t color) {
|
||||||
|
char buf[128+1];
|
||||||
|
fmt_color_ex(buf,128,term->palette,color,false);
|
||||||
|
sbuf_append(sbuf,buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
ic_private void term_append_bgcolor(term_t* term, stringbuf_t* sbuf, ic_color_t color) {
|
||||||
|
char buf[128+1];
|
||||||
|
fmt_color_ex(buf, 128, term->palette, color, true);
|
||||||
|
sbuf_append(sbuf, buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
ic_private int term_get_color_bits(term_t* term) {
|
||||||
|
switch (term->palette) {
|
||||||
|
case MONOCHROME: return 1;
|
||||||
|
case ANSI8: return 3;
|
||||||
|
case ANSI16: return 4;
|
||||||
|
case ANSI256: return 8;
|
||||||
|
case ANSIRGB: return 24;
|
||||||
|
default: return 4;
|
||||||
|
}
|
||||||
|
}
|
889
deps/isocline/src/tty.c
vendored
Normal file
889
deps/isocline/src/tty.c
vendored
Normal file
@ -0,0 +1,889 @@
|
|||||||
|
/* ----------------------------------------------------------------------------
|
||||||
|
Copyright (c) 2021, Daan Leijen
|
||||||
|
This is free software; you can redistribute it and/or modify it
|
||||||
|
under the terms of the MIT License. A copy of the license can be
|
||||||
|
found in the "LICENSE" file at the root of this distribution.
|
||||||
|
-----------------------------------------------------------------------------*/
|
||||||
|
#include <string.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdarg.h>
|
||||||
|
#include <locale.h>
|
||||||
|
|
||||||
|
#include "tty.h"
|
||||||
|
|
||||||
|
#if defined(_WIN32)
|
||||||
|
#include <windows.h>
|
||||||
|
#include <io.h>
|
||||||
|
#define isatty(fd) _isatty(fd)
|
||||||
|
#define read(fd,s,n) _read(fd,s,n)
|
||||||
|
#define STDIN_FILENO 0
|
||||||
|
#if (_WIN32_WINNT < 0x0600)
|
||||||
|
WINBASEAPI ULONGLONG WINAPI GetTickCount64(VOID);
|
||||||
|
#endif
|
||||||
|
#else
|
||||||
|
#include <signal.h>
|
||||||
|
#include <errno.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <termios.h>
|
||||||
|
#include <sys/ioctl.h>
|
||||||
|
#include <sys/select.h>
|
||||||
|
#if !defined(FIONREAD)
|
||||||
|
#include <fcntl.h>
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define TTY_PUSH_MAX (32)
|
||||||
|
|
||||||
|
struct tty_s {
|
||||||
|
int fd_in; // input handle
|
||||||
|
bool raw_enabled; // is raw mode enabled?
|
||||||
|
bool is_utf8; // is the input stream in utf-8 mode?
|
||||||
|
bool has_term_resize_event; // are resize events generated?
|
||||||
|
bool term_resize_event; // did a term resize happen?
|
||||||
|
alloc_t* mem; // memory allocator
|
||||||
|
code_t pushbuf[TTY_PUSH_MAX]; // push back buffer for full key codes
|
||||||
|
ssize_t push_count;
|
||||||
|
uint8_t cpushbuf[TTY_PUSH_MAX]; // low level push back buffer for bytes
|
||||||
|
ssize_t cpush_count;
|
||||||
|
long esc_initial_timeout; // initial ms wait to see if ESC starts an escape sequence
|
||||||
|
long esc_timeout; // follow up delay for characters in an escape sequence
|
||||||
|
#if defined(_WIN32)
|
||||||
|
HANDLE hcon; // console input handle
|
||||||
|
DWORD hcon_orig_mode; // original console mode
|
||||||
|
#else
|
||||||
|
struct termios orig_ios; // original terminal settings
|
||||||
|
struct termios raw_ios; // raw terminal settings
|
||||||
|
#endif
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
//-------------------------------------------------------------
|
||||||
|
// Forward declarations of platform dependent primitives below
|
||||||
|
//-------------------------------------------------------------
|
||||||
|
|
||||||
|
ic_private bool tty_readc_noblock(tty_t* tty, uint8_t* c, long timeout_ms); // does not modify `c` when no input (false is returned)
|
||||||
|
|
||||||
|
//-------------------------------------------------------------
|
||||||
|
// Key code helpers
|
||||||
|
//-------------------------------------------------------------
|
||||||
|
|
||||||
|
ic_private bool code_is_ascii_char(code_t c, char* chr ) {
|
||||||
|
if (c >= ' ' && c <= 0x7F) {
|
||||||
|
if (chr != NULL) *chr = (char)c;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (chr != NULL) *chr = 0;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ic_private bool code_is_unicode(code_t c, unicode_t* uchr) {
|
||||||
|
if (c <= KEY_UNICODE_MAX) {
|
||||||
|
if (uchr != NULL) *uchr = c;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (uchr != NULL) *uchr = 0;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ic_private bool code_is_virt_key(code_t c ) {
|
||||||
|
return (KEY_NO_MODS(c) <= 0x20 || KEY_NO_MODS(c) >= KEY_VIRT);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//-------------------------------------------------------------
|
||||||
|
// Read a key code
|
||||||
|
//-------------------------------------------------------------
|
||||||
|
static code_t modify_code( code_t code );
|
||||||
|
|
||||||
|
static code_t tty_read_utf8( tty_t* tty, uint8_t c0 ) {
|
||||||
|
uint8_t buf[5];
|
||||||
|
memset(buf, 0, 5);
|
||||||
|
|
||||||
|
// try to read as many bytes as potentially needed
|
||||||
|
buf[0] = c0;
|
||||||
|
ssize_t count = 1;
|
||||||
|
if (c0 > 0x7F) {
|
||||||
|
if (tty_readc_noblock(tty, buf+count, tty->esc_timeout)) {
|
||||||
|
count++;
|
||||||
|
if (c0 > 0xDF) {
|
||||||
|
if (tty_readc_noblock(tty, buf+count, tty->esc_timeout)) {
|
||||||
|
count++;
|
||||||
|
if (c0 > 0xEF) {
|
||||||
|
if (tty_readc_noblock(tty, buf+count, tty->esc_timeout)) {
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
buf[count] = 0;
|
||||||
|
debug_msg("tty: read utf8: count: %zd: %02x,%02x,%02x,%02x", count, buf[0], buf[1], buf[2], buf[3]);
|
||||||
|
|
||||||
|
// decode the utf8 to unicode
|
||||||
|
ssize_t read = 0;
|
||||||
|
code_t code = key_unicode(unicode_from_qutf8(buf, count, &read));
|
||||||
|
|
||||||
|
// push back unused bytes (in the case of invalid utf8)
|
||||||
|
while (count > read) {
|
||||||
|
count--;
|
||||||
|
if (count >= 0 && count <= 4) { // to help the static analyzer
|
||||||
|
tty_cpush_char(tty, buf[count]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return code;
|
||||||
|
}
|
||||||
|
|
||||||
|
// pop a code from the pushback buffer.
|
||||||
|
static bool tty_code_pop(tty_t* tty, code_t* code);
|
||||||
|
|
||||||
|
|
||||||
|
// read a single char/key
|
||||||
|
ic_private bool tty_read_timeout(tty_t* tty, long timeout_ms, code_t* code)
|
||||||
|
{
|
||||||
|
// is there a push_count back code?
|
||||||
|
if (tty_code_pop(tty,code)) {
|
||||||
|
return code;
|
||||||
|
}
|
||||||
|
|
||||||
|
// read a single char/byte from a character stream
|
||||||
|
uint8_t c;
|
||||||
|
if (!tty_readc_noblock(tty, &c, timeout_ms)) return false;
|
||||||
|
|
||||||
|
if (c == KEY_ESC) {
|
||||||
|
// escape sequence?
|
||||||
|
*code = tty_read_esc(tty, tty->esc_initial_timeout, tty->esc_timeout);
|
||||||
|
}
|
||||||
|
else if (c <= 0x7F) {
|
||||||
|
// ascii
|
||||||
|
*code = key_unicode(c);
|
||||||
|
}
|
||||||
|
else if (tty->is_utf8) {
|
||||||
|
// utf8 sequence
|
||||||
|
*code = tty_read_utf8(tty,c);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// c >= 0x80 but tty is not utf8; use raw plane so we can translate it back in the end
|
||||||
|
*code = key_unicode( unicode_from_raw(c) );
|
||||||
|
}
|
||||||
|
|
||||||
|
*code = modify_code(*code);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Transform virtual keys to be more portable across platforms
|
||||||
|
static code_t modify_code( code_t code ) {
|
||||||
|
code_t key = KEY_NO_MODS(code);
|
||||||
|
code_t mods = KEY_MODS(code);
|
||||||
|
debug_msg( "tty: readc %s%s%s 0x%03x ('%c')\n",
|
||||||
|
mods&KEY_MOD_SHIFT ? "shift+" : "", mods&KEY_MOD_CTRL ? "ctrl+" : "", mods&KEY_MOD_ALT ? "alt+" : "",
|
||||||
|
key, (key >= ' ' && key <= '~' ? key : ' '));
|
||||||
|
|
||||||
|
// treat KEY_RUBOUT (0x7F) as KEY_BACKSP
|
||||||
|
if (key == KEY_RUBOUT) {
|
||||||
|
code = KEY_BACKSP | mods;
|
||||||
|
}
|
||||||
|
// ctrl+'_' is translated to '\x1F' on Linux, translate it back
|
||||||
|
else if (key == key_char('\x1F') && (mods & KEY_MOD_ALT) == 0) {
|
||||||
|
key = '_';
|
||||||
|
code = WITH_CTRL(key_char('_'));
|
||||||
|
}
|
||||||
|
// treat ctrl/shift + enter always as KEY_LINEFEED for portability
|
||||||
|
else if (key == KEY_ENTER && (mods == KEY_MOD_SHIFT || mods == KEY_MOD_ALT || mods == KEY_MOD_CTRL)) {
|
||||||
|
code = KEY_LINEFEED;
|
||||||
|
}
|
||||||
|
// treat ctrl+tab always as shift+tab for portability
|
||||||
|
else if (code == WITH_CTRL(KEY_TAB)) {
|
||||||
|
code = KEY_SHIFT_TAB;
|
||||||
|
}
|
||||||
|
// treat ctrl+end/alt+>/alt-down and ctrl+home/alt+</alt-up always as pagedown/pageup for portability
|
||||||
|
else if (code == WITH_ALT(KEY_DOWN) || code == WITH_ALT('>') || code == WITH_CTRL(KEY_END)) {
|
||||||
|
code = KEY_PAGEDOWN;
|
||||||
|
}
|
||||||
|
else if (code == WITH_ALT(KEY_UP) || code == WITH_ALT('<') || code == WITH_CTRL(KEY_HOME)) {
|
||||||
|
code = KEY_PAGEUP;
|
||||||
|
}
|
||||||
|
|
||||||
|
// treat C0 codes without KEY_MOD_CTRL
|
||||||
|
if (key < ' ' && (mods&KEY_MOD_CTRL) != 0) {
|
||||||
|
code &= ~KEY_MOD_CTRL;
|
||||||
|
}
|
||||||
|
|
||||||
|
return code;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// read a single char/key
|
||||||
|
ic_private code_t tty_read(tty_t* tty)
|
||||||
|
{
|
||||||
|
code_t code;
|
||||||
|
if (!tty_read_timeout(tty, -1, &code)) return KEY_NONE;
|
||||||
|
return code;
|
||||||
|
}
|
||||||
|
|
||||||
|
//-------------------------------------------------------------
|
||||||
|
// Read back an ANSI query response
|
||||||
|
//-------------------------------------------------------------
|
||||||
|
|
||||||
|
ic_private bool tty_read_esc_response(tty_t* tty, char esc_start, bool final_st, char* buf, ssize_t buflen )
|
||||||
|
{
|
||||||
|
buf[0] = 0;
|
||||||
|
ssize_t len = 0;
|
||||||
|
uint8_t c = 0;
|
||||||
|
if (!tty_readc_noblock(tty, &c, 2*tty->esc_initial_timeout) || c != '\x1B') {
|
||||||
|
debug_msg("initial esc response failed: 0x%02x\n", c);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!tty_readc_noblock(tty, &c, tty->esc_timeout) || (c != esc_start)) return false;
|
||||||
|
while( len < buflen ) {
|
||||||
|
if (!tty_readc_noblock(tty, &c, tty->esc_timeout)) return false;
|
||||||
|
if (final_st) {
|
||||||
|
// OSC is terminated by BELL, or ESC \ (ST) (and STX)
|
||||||
|
if (c=='\x07' || c=='\x02') {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else if (c=='\x1B') {
|
||||||
|
uint8_t c1;
|
||||||
|
if (!tty_readc_noblock(tty, &c1, tty->esc_timeout)) return false;
|
||||||
|
if (c1=='\\') break;
|
||||||
|
tty_cpush_char(tty,c1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (c == '\x02') { // STX
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else if (!((c >= '0' && c <= '9') || strchr("<=>?;:",c) != NULL)) {
|
||||||
|
buf[len++] = (char)c; // for non-OSC save the terminating character
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
buf[len++] = (char)c;
|
||||||
|
}
|
||||||
|
buf[len] = 0;
|
||||||
|
debug_msg("tty: escape query response: %s\n", buf);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
//-------------------------------------------------------------
|
||||||
|
// High level code pushback
|
||||||
|
//-------------------------------------------------------------
|
||||||
|
|
||||||
|
static bool tty_code_pop( tty_t* tty, code_t* code ) {
|
||||||
|
if (tty->push_count <= 0) return false;
|
||||||
|
tty->push_count--;
|
||||||
|
*code = tty->pushbuf[tty->push_count];
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
ic_private void tty_code_pushback( tty_t* tty, code_t c ) {
|
||||||
|
// note: must be signal safe
|
||||||
|
if (tty->push_count >= TTY_PUSH_MAX) return;
|
||||||
|
tty->pushbuf[tty->push_count] = c;
|
||||||
|
tty->push_count++;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//-------------------------------------------------------------
|
||||||
|
// low-level character pushback (for escape sequences and windows)
|
||||||
|
//-------------------------------------------------------------
|
||||||
|
|
||||||
|
ic_private bool tty_cpop(tty_t* tty, uint8_t* c) {
|
||||||
|
if (tty->cpush_count <= 0) { // do not modify c on failure (see `tty_decode_unicode`)
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
tty->cpush_count--;
|
||||||
|
*c = tty->cpushbuf[tty->cpush_count];
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void tty_cpush(tty_t* tty, const char* s) {
|
||||||
|
ssize_t len = ic_strlen(s);
|
||||||
|
if (tty->push_count + len > TTY_PUSH_MAX) {
|
||||||
|
debug_msg("tty: cpush buffer full! (pushing %s)\n", s);
|
||||||
|
assert(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (ssize_t i = 0; i < len; i++) {
|
||||||
|
tty->cpushbuf[tty->cpush_count + i] = (uint8_t)( s[len - i - 1] );
|
||||||
|
}
|
||||||
|
tty->cpush_count += len;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// convenience function for small sequences
|
||||||
|
static void tty_cpushf(tty_t* tty, const char* fmt, ...) {
|
||||||
|
va_list args;
|
||||||
|
va_start(args,fmt);
|
||||||
|
char buf[TTY_PUSH_MAX+1];
|
||||||
|
vsnprintf(buf,TTY_PUSH_MAX,fmt,args);
|
||||||
|
buf[TTY_PUSH_MAX] = 0;
|
||||||
|
tty_cpush(tty,buf);
|
||||||
|
va_end(args);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ic_private void tty_cpush_char(tty_t* tty, uint8_t c) {
|
||||||
|
uint8_t buf[2];
|
||||||
|
buf[0] = c;
|
||||||
|
buf[1] = 0;
|
||||||
|
tty_cpush(tty, (const char*)buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//-------------------------------------------------------------
|
||||||
|
// Push escape codes (used on Windows to insert keys)
|
||||||
|
//-------------------------------------------------------------
|
||||||
|
|
||||||
|
static unsigned csi_mods(code_t mods) {
|
||||||
|
unsigned m = 1;
|
||||||
|
if (mods&KEY_MOD_SHIFT) m += 1;
|
||||||
|
if (mods&KEY_MOD_ALT) m += 2;
|
||||||
|
if (mods&KEY_MOD_CTRL) m += 4;
|
||||||
|
return m;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Push ESC [ <vtcode> ; <mods> ~
|
||||||
|
static void tty_cpush_csi_vt( tty_t* tty, code_t mods, uint32_t vtcode ) {
|
||||||
|
tty_cpushf(tty,"\x1B[%u;%u~", vtcode, csi_mods(mods) );
|
||||||
|
}
|
||||||
|
|
||||||
|
// push ESC [ 1 ; <mods> <xcmd>
|
||||||
|
static void tty_cpush_csi_xterm( tty_t* tty, code_t mods, char xcode ) {
|
||||||
|
tty_cpushf(tty,"\x1B[1;%u%c", csi_mods(mods), xcode );
|
||||||
|
}
|
||||||
|
|
||||||
|
// push ESC [ <unicode> ; <mods> u
|
||||||
|
static void tty_cpush_csi_unicode( tty_t* tty, code_t mods, uint32_t unicode ) {
|
||||||
|
if ((unicode < 0x80 && mods == 0) ||
|
||||||
|
(mods == KEY_MOD_CTRL && unicode < ' ' && unicode != KEY_TAB && unicode != KEY_ENTER
|
||||||
|
&& unicode != KEY_LINEFEED && unicode != KEY_BACKSP) ||
|
||||||
|
(mods == KEY_MOD_SHIFT && unicode >= ' ' && unicode <= KEY_RUBOUT)) {
|
||||||
|
tty_cpush_char(tty,(uint8_t)unicode);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
tty_cpushf(tty,"\x1B[%u;%uu", unicode, csi_mods(mods) );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//-------------------------------------------------------------
|
||||||
|
// Init
|
||||||
|
//-------------------------------------------------------------
|
||||||
|
|
||||||
|
static bool tty_init_raw(tty_t* tty);
|
||||||
|
static void tty_done_raw(tty_t* tty);
|
||||||
|
|
||||||
|
static bool tty_init_utf8(tty_t* tty) {
|
||||||
|
#ifdef _WIN32
|
||||||
|
tty->is_utf8 = true;
|
||||||
|
#else
|
||||||
|
const char* loc = setlocale(LC_ALL,"");
|
||||||
|
tty->is_utf8 = (ic_icontains(loc,"UTF-8") || ic_icontains(loc,"utf8") || ic_stricmp(loc,"C") == 0);
|
||||||
|
debug_msg("tty: utf8: %s (loc=%s)\n", tty->is_utf8 ? "true" : "false", loc);
|
||||||
|
#endif
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
ic_private tty_t* tty_new(alloc_t* mem, int fd_in)
|
||||||
|
{
|
||||||
|
tty_t* tty = mem_zalloc_tp(mem, tty_t);
|
||||||
|
tty->mem = mem;
|
||||||
|
tty->fd_in = (fd_in < 0 ? STDIN_FILENO : fd_in);
|
||||||
|
#if defined(__APPLE__)
|
||||||
|
tty->esc_initial_timeout = 200; // apple use ESC+<key> for alt-<key>
|
||||||
|
#else
|
||||||
|
tty->esc_initial_timeout = 100;
|
||||||
|
#endif
|
||||||
|
tty->esc_timeout = 10;
|
||||||
|
if (!(isatty(tty->fd_in) && tty_init_raw(tty) && tty_init_utf8(tty))) {
|
||||||
|
tty_free(tty);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
return tty;
|
||||||
|
}
|
||||||
|
|
||||||
|
ic_private void tty_free(tty_t* tty) {
|
||||||
|
if (tty==NULL) return;
|
||||||
|
tty_end_raw(tty);
|
||||||
|
tty_done_raw(tty);
|
||||||
|
mem_free(tty->mem,tty);
|
||||||
|
}
|
||||||
|
|
||||||
|
ic_private bool tty_is_utf8(const tty_t* tty) {
|
||||||
|
if (tty == NULL) return true;
|
||||||
|
return (tty->is_utf8);
|
||||||
|
}
|
||||||
|
|
||||||
|
ic_private bool tty_term_resize_event(tty_t* tty) {
|
||||||
|
if (tty == NULL) return true;
|
||||||
|
if (tty->has_term_resize_event) {
|
||||||
|
if (!tty->term_resize_event) return false;
|
||||||
|
tty->term_resize_event = false; // reset.
|
||||||
|
}
|
||||||
|
return true; // always return true on systems without a resize event (more expensive but still ok)
|
||||||
|
}
|
||||||
|
|
||||||
|
ic_private void tty_set_esc_delay(tty_t* tty, long initial_delay_ms, long followup_delay_ms) {
|
||||||
|
tty->esc_initial_timeout = (initial_delay_ms < 0 ? 0 : (initial_delay_ms > 1000 ? 1000 : initial_delay_ms));
|
||||||
|
tty->esc_timeout = (followup_delay_ms < 0 ? 0 : (followup_delay_ms > 1000 ? 1000 : followup_delay_ms));
|
||||||
|
}
|
||||||
|
|
||||||
|
//-------------------------------------------------------------
|
||||||
|
// Unix
|
||||||
|
//-------------------------------------------------------------
|
||||||
|
#if !defined(_WIN32)
|
||||||
|
|
||||||
|
static bool tty_readc_blocking(tty_t* tty, uint8_t* c) {
|
||||||
|
if (tty_cpop(tty,c)) return true;
|
||||||
|
*c = 0;
|
||||||
|
ssize_t nread = read(tty->fd_in, (char*)c, 1);
|
||||||
|
if (nread < 0 && errno == EINTR) {
|
||||||
|
// can happen on SIGWINCH signal for terminal resize
|
||||||
|
}
|
||||||
|
return (nread == 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// non blocking read -- with a small timeout used for reading escape sequences.
|
||||||
|
ic_private bool tty_readc_noblock(tty_t* tty, uint8_t* c, long timeout_ms)
|
||||||
|
{
|
||||||
|
// in our pushback buffer?
|
||||||
|
if (tty_cpop(tty, c)) return true;
|
||||||
|
|
||||||
|
// blocking read?
|
||||||
|
if (timeout_ms < 0) {
|
||||||
|
return tty_readc_blocking(tty,c);
|
||||||
|
}
|
||||||
|
|
||||||
|
// if supported, peek first if any char is available.
|
||||||
|
#if defined(FIONREAD)
|
||||||
|
{ int navail = 0;
|
||||||
|
if (ioctl(0, FIONREAD, &navail) == 0) {
|
||||||
|
if (navail >= 1) {
|
||||||
|
return tty_readc_blocking(tty, c);
|
||||||
|
}
|
||||||
|
else if (timeout_ms == 0) {
|
||||||
|
return false; // return early if there is no input available (with a zero timeout)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// otherwise block for at most timeout milliseconds
|
||||||
|
#if defined(FD_SET)
|
||||||
|
// we can use select to detect when input becomes available
|
||||||
|
fd_set readset;
|
||||||
|
struct timeval time;
|
||||||
|
FD_ZERO(&readset);
|
||||||
|
FD_SET(tty->fd_in, &readset);
|
||||||
|
time.tv_sec = (timeout_ms > 0 ? timeout_ms / 1000 : 0);
|
||||||
|
time.tv_usec = (timeout_ms > 0 ? 1000*(timeout_ms % 1000) : 0);
|
||||||
|
if (select(tty->fd_in + 1, &readset, NULL, NULL, &time) == 1) {
|
||||||
|
// input available
|
||||||
|
return tty_readc_blocking(tty, c);
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
// no select, we cannot timeout; use usleeps :-(
|
||||||
|
// todo: this seems very rare nowadays; should be even support this?
|
||||||
|
do {
|
||||||
|
// peek ahead if possible
|
||||||
|
#if defined(FIONREAD)
|
||||||
|
int navail = 0;
|
||||||
|
if (ioctl(0, FIONREAD, &navail) == 0 && navail >= 1) {
|
||||||
|
return tty_readc_blocking(tty, c);
|
||||||
|
}
|
||||||
|
#elif defined(O_NONBLOCK)
|
||||||
|
// use a temporary non-blocking read mode
|
||||||
|
int fstatus = fcntl(tty->fd_in, F_GETFL, 0);
|
||||||
|
if (fstatus != -1) {
|
||||||
|
if (fcntl(tty->fd_in, F_SETFL, (fstatus | O_NONBLOCK)) != -1) {
|
||||||
|
char buf[2] = { 0, 0 };
|
||||||
|
ssize_t nread = read(tty->fd_in, buf, 1);
|
||||||
|
fcntl(tty->fd_in, F_SETFL, fstatus);
|
||||||
|
if (nread >= 1) {
|
||||||
|
*c = (uint8_t)buf[0];
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
#error "define an nonblocking read for this platform"
|
||||||
|
#endif
|
||||||
|
// and sleep a bit
|
||||||
|
if (timeout_ms > 0) {
|
||||||
|
usleep(50*1000L); // sleep at most 0.05s at a time
|
||||||
|
timeout_ms -= 100;
|
||||||
|
if (timeout_ms < 0) { timeout_ms = 0; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
while (timeout_ms > 0);
|
||||||
|
#endif
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
#if defined(TIOCSTI)
|
||||||
|
ic_private bool tty_async_stop(const tty_t* tty) {
|
||||||
|
// insert ^C in the input stream
|
||||||
|
char c = KEY_CTRL_C;
|
||||||
|
return (ioctl(tty->fd_in, TIOCSTI, &c) >= 0);
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
ic_private bool tty_async_stop(const tty_t* tty) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// We install various signal handlers to restore the terminal settings
|
||||||
|
// in case of a terminating signal. This is also used to catch terminal window resizes.
|
||||||
|
// This is not strictly needed so this can be disabled on
|
||||||
|
// (older) platforms that do not support signal handling well.
|
||||||
|
#if defined(SIGWINCH) && defined(SA_RESTART) // ensure basic signal functionality is defined
|
||||||
|
|
||||||
|
// store the tty in a global so we access it on unexpected termination
|
||||||
|
static tty_t* sig_tty; // = NULL
|
||||||
|
|
||||||
|
// Catch all termination signals (and SIGWINCH)
|
||||||
|
typedef struct signal_handler_s {
|
||||||
|
int signum;
|
||||||
|
union {
|
||||||
|
int _avoid_warning;
|
||||||
|
struct sigaction previous;
|
||||||
|
} action;
|
||||||
|
} signal_handler_t;
|
||||||
|
|
||||||
|
static signal_handler_t sighandlers[] = {
|
||||||
|
{ SIGWINCH, {0} },
|
||||||
|
{ SIGTERM , {0} },
|
||||||
|
{ SIGINT , {0} },
|
||||||
|
{ SIGQUIT , {0} },
|
||||||
|
{ SIGHUP , {0} },
|
||||||
|
{ SIGSEGV , {0} },
|
||||||
|
{ SIGTRAP , {0} },
|
||||||
|
{ SIGBUS , {0} },
|
||||||
|
{ SIGTSTP , {0} },
|
||||||
|
{ SIGTTIN , {0} },
|
||||||
|
{ SIGTTOU , {0} },
|
||||||
|
{ 0 , {0} }
|
||||||
|
};
|
||||||
|
|
||||||
|
static bool sigaction_is_valid( struct sigaction* sa ) {
|
||||||
|
return (sa->sa_sigaction != NULL && sa->sa_handler != SIG_DFL && sa->sa_handler != SIG_IGN);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generic signal handler
|
||||||
|
static void sig_handler(int signum, siginfo_t* siginfo, void* uap ) {
|
||||||
|
if (signum == SIGWINCH) {
|
||||||
|
if (sig_tty != NULL) {
|
||||||
|
sig_tty->term_resize_event = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// the rest are termination signals; restore the terminal mode. (`tcsetattr` is signal-safe)
|
||||||
|
if (sig_tty != NULL && sig_tty->raw_enabled) {
|
||||||
|
tcsetattr(sig_tty->fd_in, TCSAFLUSH, &sig_tty->orig_ios);
|
||||||
|
sig_tty->raw_enabled = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// call previous handler
|
||||||
|
signal_handler_t* sh = sighandlers;
|
||||||
|
while( sh->signum != 0 && sh->signum != signum) { sh++; }
|
||||||
|
if (sh->signum == signum) {
|
||||||
|
if (sigaction_is_valid(&sh->action.previous)) {
|
||||||
|
(sh->action.previous.sa_sigaction)(signum, siginfo, uap);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void signals_install(tty_t* tty) {
|
||||||
|
sig_tty = tty;
|
||||||
|
// generic signal handler
|
||||||
|
struct sigaction handler;
|
||||||
|
memset(&handler,0,sizeof(handler));
|
||||||
|
sigemptyset(&handler.sa_mask);
|
||||||
|
handler.sa_sigaction = &sig_handler;
|
||||||
|
handler.sa_flags = SA_RESTART;
|
||||||
|
// install for all signals
|
||||||
|
for( signal_handler_t* sh = sighandlers; sh->signum != 0; sh++ ) {
|
||||||
|
if (sigaction( sh->signum, NULL, &sh->action.previous) == 0) { // get previous
|
||||||
|
if (sh->action.previous.sa_handler != SIG_IGN) { // if not to be ignored
|
||||||
|
if (sigaction( sh->signum, &handler, &sh->action.previous ) < 0) { // install our handler
|
||||||
|
sh->action.previous.sa_sigaction = NULL; // do not restore on error
|
||||||
|
}
|
||||||
|
else if (sh->signum == SIGWINCH) {
|
||||||
|
sig_tty->has_term_resize_event = true;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void signals_restore(void) {
|
||||||
|
// restore all signal handlers
|
||||||
|
for( signal_handler_t* sh = sighandlers; sh->signum != 0; sh++ ) {
|
||||||
|
if (sigaction_is_valid(&sh->action.previous)) {
|
||||||
|
sigaction( sh->signum, &sh->action.previous, NULL );
|
||||||
|
};
|
||||||
|
}
|
||||||
|
sig_tty = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
#else
|
||||||
|
static void signals_install(tty_t* tty) {
|
||||||
|
ic_unused(tty);
|
||||||
|
// nothing
|
||||||
|
}
|
||||||
|
static void signals_restore(void) {
|
||||||
|
// nothing
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
ic_private bool tty_start_raw(tty_t* tty) {
|
||||||
|
if (tty == NULL) return false;
|
||||||
|
if (tty->raw_enabled) return true;
|
||||||
|
if (tcsetattr(tty->fd_in,TCSAFLUSH,&tty->raw_ios) < 0) return false;
|
||||||
|
tty->raw_enabled = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
ic_private void tty_end_raw(tty_t* tty) {
|
||||||
|
if (tty == NULL) return;
|
||||||
|
if (!tty->raw_enabled) return;
|
||||||
|
tty->cpush_count = 0;
|
||||||
|
if (tcsetattr(tty->fd_in,TCSAFLUSH,&tty->orig_ios) < 0) return;
|
||||||
|
tty->raw_enabled = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool tty_init_raw(tty_t* tty)
|
||||||
|
{
|
||||||
|
// Set input to raw mode. See <https://man7.org/linux/man-pages/man3/termios.3.html>.
|
||||||
|
if (tcgetattr(tty->fd_in,&tty->orig_ios) == -1) return false;
|
||||||
|
tty->raw_ios = tty->orig_ios;
|
||||||
|
// input: no break signal, no \r to \n, no parity check, no 8-bit to 7-bit, no flow control
|
||||||
|
tty->raw_ios.c_iflag &= ~(unsigned long)(BRKINT | ICRNL | INPCK | ISTRIP | IXON);
|
||||||
|
// control: allow 8-bit
|
||||||
|
tty->raw_ios.c_cflag |= CS8;
|
||||||
|
// local: no echo, no line-by-line (canonical), no extended input processing, no signals for ^z,^c
|
||||||
|
tty->raw_ios.c_lflag &= ~(unsigned long)(ECHO | ICANON | IEXTEN | ISIG);
|
||||||
|
// 1 byte at a time, no delay
|
||||||
|
tty->raw_ios.c_cc[VTIME] = 0;
|
||||||
|
tty->raw_ios.c_cc[VMIN] = 1;
|
||||||
|
|
||||||
|
// store in global so our signal handlers can restore the terminal mode
|
||||||
|
signals_install(tty);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void tty_done_raw(tty_t* tty) {
|
||||||
|
ic_unused(tty);
|
||||||
|
signals_restore();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#else
|
||||||
|
|
||||||
|
//-------------------------------------------------------------
|
||||||
|
// Windows
|
||||||
|
// For best portability we push CSI escape sequences directly
|
||||||
|
// to the character stream (instead of returning key codes).
|
||||||
|
//-------------------------------------------------------------
|
||||||
|
|
||||||
|
static void tty_waitc_console(tty_t* tty, long timeout_ms);
|
||||||
|
|
||||||
|
ic_private bool tty_readc_noblock(tty_t* tty, uint8_t* c, long timeout_ms) { // don't modify `c` if there is no input
|
||||||
|
// in our pushback buffer?
|
||||||
|
if (tty_cpop(tty, c)) return true;
|
||||||
|
// any events in the input queue?
|
||||||
|
tty_waitc_console(tty, timeout_ms);
|
||||||
|
return tty_cpop(tty, c);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read from the console input events and push escape codes into the tty cbuffer.
|
||||||
|
static void tty_waitc_console(tty_t* tty, long timeout_ms)
|
||||||
|
{
|
||||||
|
// wait for a key down event
|
||||||
|
INPUT_RECORD inp;
|
||||||
|
DWORD count;
|
||||||
|
uint32_t surrogate_hi = 0;
|
||||||
|
while (true) {
|
||||||
|
// check if there are events if in non-blocking timeout mode
|
||||||
|
if (timeout_ms >= 0) {
|
||||||
|
// first peek ahead
|
||||||
|
if (!GetNumberOfConsoleInputEvents(tty->hcon, &count)) return;
|
||||||
|
if (count == 0) {
|
||||||
|
if (timeout_ms == 0) {
|
||||||
|
// out of time
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// wait for input events for at most timeout milli seconds
|
||||||
|
ULONGLONG start_ms = GetTickCount64();
|
||||||
|
DWORD res = WaitForSingleObject(tty->hcon, (DWORD)timeout_ms);
|
||||||
|
switch (res) {
|
||||||
|
case WAIT_OBJECT_0: {
|
||||||
|
// input is available, decrease our timeout
|
||||||
|
ULONGLONG waited_ms = (GetTickCount64() - start_ms);
|
||||||
|
timeout_ms -= (long)waited_ms;
|
||||||
|
if (timeout_ms < 0) {
|
||||||
|
timeout_ms = 0;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case WAIT_TIMEOUT:
|
||||||
|
case WAIT_ABANDONED:
|
||||||
|
case WAIT_FAILED:
|
||||||
|
default:
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// (blocking) Read from the input
|
||||||
|
if (!ReadConsoleInputW(tty->hcon, &inp, 1, &count)) return;
|
||||||
|
if (count != 1) return;
|
||||||
|
|
||||||
|
// resize event?
|
||||||
|
if (inp.EventType == WINDOW_BUFFER_SIZE_EVENT) {
|
||||||
|
tty->term_resize_event = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// wait for key down events
|
||||||
|
if (inp.EventType != KEY_EVENT) continue;
|
||||||
|
|
||||||
|
// the modifier state
|
||||||
|
DWORD modstate = inp.Event.KeyEvent.dwControlKeyState;
|
||||||
|
|
||||||
|
// we need to handle shift up events separately
|
||||||
|
if (!inp.Event.KeyEvent.bKeyDown && inp.Event.KeyEvent.wVirtualKeyCode == VK_SHIFT) {
|
||||||
|
modstate &= (DWORD)~SHIFT_PRESSED;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ignore AltGr
|
||||||
|
DWORD altgr = LEFT_CTRL_PRESSED | RIGHT_ALT_PRESSED;
|
||||||
|
if ((modstate & altgr) == altgr) { modstate &= ~altgr; }
|
||||||
|
|
||||||
|
|
||||||
|
// get modifiers
|
||||||
|
code_t mods = 0;
|
||||||
|
if ((modstate & ( RIGHT_CTRL_PRESSED | LEFT_CTRL_PRESSED )) != 0) mods |= KEY_MOD_CTRL;
|
||||||
|
if ((modstate & ( RIGHT_ALT_PRESSED | LEFT_ALT_PRESSED )) != 0) mods |= KEY_MOD_ALT;
|
||||||
|
if ((modstate & SHIFT_PRESSED) != 0) mods |= KEY_MOD_SHIFT;
|
||||||
|
|
||||||
|
// virtual keys
|
||||||
|
uint32_t chr = (uint32_t)inp.Event.KeyEvent.uChar.UnicodeChar;
|
||||||
|
WORD virt = inp.Event.KeyEvent.wVirtualKeyCode;
|
||||||
|
debug_msg("tty: console %s: %s%s%s virt 0x%04x, chr 0x%04x ('%c')\n", inp.Event.KeyEvent.bKeyDown ? "down" : "up", mods&KEY_MOD_CTRL ? "ctrl-" : "", mods&KEY_MOD_ALT ? "alt-" : "", mods&KEY_MOD_SHIFT ? "shift-" : "", virt, chr, chr);
|
||||||
|
|
||||||
|
// only process keydown events (except for Alt-up which is used for unicode pasting...)
|
||||||
|
if (!inp.Event.KeyEvent.bKeyDown && virt != VK_MENU) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (chr == 0) {
|
||||||
|
switch (virt) {
|
||||||
|
case VK_UP: tty_cpush_csi_xterm(tty, mods, 'A'); return;
|
||||||
|
case VK_DOWN: tty_cpush_csi_xterm(tty, mods, 'B'); return;
|
||||||
|
case VK_RIGHT: tty_cpush_csi_xterm(tty, mods, 'C'); return;
|
||||||
|
case VK_LEFT: tty_cpush_csi_xterm(tty, mods, 'D'); return;
|
||||||
|
case VK_END: tty_cpush_csi_xterm(tty, mods, 'F'); return;
|
||||||
|
case VK_HOME: tty_cpush_csi_xterm(tty, mods, 'H'); return;
|
||||||
|
case VK_DELETE: tty_cpush_csi_vt(tty,mods,3); return;
|
||||||
|
case VK_PRIOR: tty_cpush_csi_vt(tty,mods,5); return; //page up
|
||||||
|
case VK_NEXT: tty_cpush_csi_vt(tty,mods,6); return; //page down
|
||||||
|
case VK_TAB: tty_cpush_csi_unicode(tty,mods,9); return;
|
||||||
|
case VK_RETURN: tty_cpush_csi_unicode(tty,mods,13); return;
|
||||||
|
default: {
|
||||||
|
uint32_t vtcode = 0;
|
||||||
|
if (virt >= VK_F1 && virt <= VK_F5) {
|
||||||
|
vtcode = 10 + (virt - VK_F1);
|
||||||
|
}
|
||||||
|
else if (virt >= VK_F6 && virt <= VK_F10) {
|
||||||
|
vtcode = 17 + (virt - VK_F6);
|
||||||
|
}
|
||||||
|
else if (virt >= VK_F11 && virt <= VK_F12) {
|
||||||
|
vtcode = 13 + (virt - VK_F11);
|
||||||
|
}
|
||||||
|
if (vtcode > 0) {
|
||||||
|
tty_cpush_csi_vt(tty,mods,vtcode);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// ignore other control keys (shift etc).
|
||||||
|
}
|
||||||
|
// high surrogate pair
|
||||||
|
else if (chr >= 0xD800 && chr <= 0xDBFF) {
|
||||||
|
surrogate_hi = (chr - 0xD800);
|
||||||
|
}
|
||||||
|
// low surrogate pair
|
||||||
|
else if (chr >= 0xDC00 && chr <= 0xDFFF) {
|
||||||
|
chr = ((surrogate_hi << 10) + (chr - 0xDC00) + 0x10000);
|
||||||
|
tty_cpush_csi_unicode(tty,mods,chr);
|
||||||
|
surrogate_hi = 0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// regular character
|
||||||
|
else {
|
||||||
|
tty_cpush_csi_unicode(tty,mods,chr);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ic_private bool tty_async_stop(const tty_t* tty) {
|
||||||
|
// send ^c
|
||||||
|
INPUT_RECORD events[2];
|
||||||
|
memset(events, 0, 2*sizeof(INPUT_RECORD));
|
||||||
|
events[0].EventType = KEY_EVENT;
|
||||||
|
events[0].Event.KeyEvent.bKeyDown = TRUE;
|
||||||
|
events[0].Event.KeyEvent.uChar.AsciiChar = KEY_CTRL_C;
|
||||||
|
events[1] = events[0];
|
||||||
|
events[1].Event.KeyEvent.bKeyDown = FALSE;
|
||||||
|
DWORD nwritten = 0;
|
||||||
|
WriteConsoleInput(tty->hcon, events, 2, &nwritten);
|
||||||
|
return (nwritten == 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
ic_private bool tty_start_raw(tty_t* tty) {
|
||||||
|
if (tty->raw_enabled) return true;
|
||||||
|
GetConsoleMode(tty->hcon,&tty->hcon_orig_mode);
|
||||||
|
DWORD mode = ENABLE_QUICK_EDIT_MODE // cut&paste allowed
|
||||||
|
| ENABLE_WINDOW_INPUT // to catch resize events
|
||||||
|
// | ENABLE_VIRTUAL_TERMINAL_INPUT
|
||||||
|
// | ENABLE_PROCESSED_INPUT
|
||||||
|
;
|
||||||
|
SetConsoleMode(tty->hcon, mode );
|
||||||
|
tty->raw_enabled = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
ic_private void tty_end_raw(tty_t* tty) {
|
||||||
|
if (!tty->raw_enabled) return;
|
||||||
|
SetConsoleMode(tty->hcon, tty->hcon_orig_mode );
|
||||||
|
tty->raw_enabled = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool tty_init_raw(tty_t* tty) {
|
||||||
|
tty->hcon = GetStdHandle( STD_INPUT_HANDLE );
|
||||||
|
tty->has_term_resize_event = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void tty_done_raw(tty_t* tty) {
|
||||||
|
ic_unused(tty);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
160
deps/isocline/src/tty.h
vendored
Normal file
160
deps/isocline/src/tty.h
vendored
Normal file
@ -0,0 +1,160 @@
|
|||||||
|
/* ----------------------------------------------------------------------------
|
||||||
|
Copyright (c) 2021, Daan Leijen
|
||||||
|
This is free software; you can redistribute it and/or modify it
|
||||||
|
under the terms of the MIT License. A copy of the license can be
|
||||||
|
found in the "LICENSE" file at the root of this distribution.
|
||||||
|
-----------------------------------------------------------------------------*/
|
||||||
|
#pragma once
|
||||||
|
#ifndef IC_TTY_H
|
||||||
|
#define IC_TTY_H
|
||||||
|
|
||||||
|
#include "common.h"
|
||||||
|
|
||||||
|
//-------------------------------------------------------------
|
||||||
|
// TTY/Keyboard input
|
||||||
|
//-------------------------------------------------------------
|
||||||
|
|
||||||
|
// Key code
|
||||||
|
typedef uint32_t code_t;
|
||||||
|
|
||||||
|
// TTY interface
|
||||||
|
struct tty_s;
|
||||||
|
typedef struct tty_s tty_t;
|
||||||
|
|
||||||
|
|
||||||
|
ic_private tty_t* tty_new(alloc_t* mem, int fd_in);
|
||||||
|
ic_private void tty_free(tty_t* tty);
|
||||||
|
|
||||||
|
ic_private bool tty_is_utf8(const tty_t* tty);
|
||||||
|
ic_private bool tty_start_raw(tty_t* tty);
|
||||||
|
ic_private void tty_end_raw(tty_t* tty);
|
||||||
|
ic_private code_t tty_read(tty_t* tty);
|
||||||
|
ic_private bool tty_read_timeout(tty_t* tty, long timeout_ms, code_t* c );
|
||||||
|
|
||||||
|
ic_private void tty_code_pushback( tty_t* tty, code_t c );
|
||||||
|
ic_private bool code_is_ascii_char(code_t c, char* chr );
|
||||||
|
ic_private bool code_is_unicode(code_t c, unicode_t* uchr);
|
||||||
|
ic_private bool code_is_virt_key(code_t c );
|
||||||
|
|
||||||
|
ic_private bool tty_term_resize_event(tty_t* tty); // did the terminal resize?
|
||||||
|
ic_private bool tty_async_stop(const tty_t* tty); // unblock the read asynchronously
|
||||||
|
ic_private void tty_set_esc_delay(tty_t* tty, long initial_delay_ms, long followup_delay_ms);
|
||||||
|
|
||||||
|
// shared between tty.c and tty_esc.c: low level character push
|
||||||
|
ic_private void tty_cpush_char(tty_t* tty, uint8_t c);
|
||||||
|
ic_private bool tty_cpop(tty_t* tty, uint8_t* c);
|
||||||
|
ic_private bool tty_readc_noblock(tty_t* tty, uint8_t* c, long timeout_ms);
|
||||||
|
ic_private code_t tty_read_esc(tty_t* tty, long esc_initial_timeout, long esc_timeout); // in tty_esc.c
|
||||||
|
|
||||||
|
// used by term.c to read back ANSI escape responses
|
||||||
|
ic_private bool tty_read_esc_response(tty_t* tty, char esc_start, bool final_st, char* buf, ssize_t buflen );
|
||||||
|
|
||||||
|
|
||||||
|
//-------------------------------------------------------------
|
||||||
|
// Key codes: a code_t is 32 bits.
|
||||||
|
// we use the bottom 24 (nah, 21) bits for unicode (up to x0010FFFF)
|
||||||
|
// The codes after x01000000 are for virtual keys
|
||||||
|
// and events use x02000000.
|
||||||
|
// The top 4 bits are used for modifiers.
|
||||||
|
//-------------------------------------------------------------
|
||||||
|
|
||||||
|
static inline code_t key_char( char c ) {
|
||||||
|
// careful about signed character conversion (negative char ~> 0x80 - 0xFF)
|
||||||
|
return ((uint8_t)c);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline code_t key_unicode( unicode_t u ) {
|
||||||
|
return u;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#define KEY_MOD_SHIFT (0x10000000U)
|
||||||
|
#define KEY_MOD_ALT (0x20000000U)
|
||||||
|
#define KEY_MOD_CTRL (0x40000000U)
|
||||||
|
|
||||||
|
#define KEY_NO_MODS(k) (k & 0x0FFFFFFFU)
|
||||||
|
#define KEY_MODS(k) (k & 0xF0000000U)
|
||||||
|
|
||||||
|
#define WITH_SHIFT(x) (x | KEY_MOD_SHIFT)
|
||||||
|
#define WITH_ALT(x) (x | KEY_MOD_ALT)
|
||||||
|
#define WITH_CTRL(x) (x | KEY_MOD_CTRL)
|
||||||
|
|
||||||
|
#define KEY_NONE (0)
|
||||||
|
#define KEY_CTRL_A (1)
|
||||||
|
#define KEY_CTRL_B (2)
|
||||||
|
#define KEY_CTRL_C (3)
|
||||||
|
#define KEY_CTRL_D (4)
|
||||||
|
#define KEY_CTRL_E (5)
|
||||||
|
#define KEY_CTRL_F (6)
|
||||||
|
#define KEY_BELL (7)
|
||||||
|
#define KEY_BACKSP (8)
|
||||||
|
#define KEY_TAB (9)
|
||||||
|
#define KEY_LINEFEED (10) // ctrl/shift + enter is considered KEY_LINEFEED
|
||||||
|
#define KEY_CTRL_K (11)
|
||||||
|
#define KEY_CTRL_L (12)
|
||||||
|
#define KEY_ENTER (13)
|
||||||
|
#define KEY_CTRL_N (14)
|
||||||
|
#define KEY_CTRL_O (15)
|
||||||
|
#define KEY_CTRL_P (16)
|
||||||
|
#define KEY_CTRL_Q (17)
|
||||||
|
#define KEY_CTRL_R (18)
|
||||||
|
#define KEY_CTRL_S (19)
|
||||||
|
#define KEY_CTRL_T (20)
|
||||||
|
#define KEY_CTRL_U (21)
|
||||||
|
#define KEY_CTRL_V (22)
|
||||||
|
#define KEY_CTRL_W (23)
|
||||||
|
#define KEY_CTRL_X (24)
|
||||||
|
#define KEY_CTRL_Y (25)
|
||||||
|
#define KEY_CTRL_Z (26)
|
||||||
|
#define KEY_ESC (27)
|
||||||
|
#define KEY_SPACE (32)
|
||||||
|
#define KEY_RUBOUT (127) // always translated to KEY_BACKSP
|
||||||
|
#define KEY_UNICODE_MAX (0x0010FFFFU)
|
||||||
|
|
||||||
|
|
||||||
|
#define KEY_VIRT (0x01000000U)
|
||||||
|
#define KEY_UP (KEY_VIRT+0)
|
||||||
|
#define KEY_DOWN (KEY_VIRT+1)
|
||||||
|
#define KEY_LEFT (KEY_VIRT+2)
|
||||||
|
#define KEY_RIGHT (KEY_VIRT+3)
|
||||||
|
#define KEY_HOME (KEY_VIRT+4)
|
||||||
|
#define KEY_END (KEY_VIRT+5)
|
||||||
|
#define KEY_DEL (KEY_VIRT+6)
|
||||||
|
#define KEY_PAGEUP (KEY_VIRT+7)
|
||||||
|
#define KEY_PAGEDOWN (KEY_VIRT+8)
|
||||||
|
#define KEY_INS (KEY_VIRT+9)
|
||||||
|
|
||||||
|
#define KEY_F1 (KEY_VIRT+11)
|
||||||
|
#define KEY_F2 (KEY_VIRT+12)
|
||||||
|
#define KEY_F3 (KEY_VIRT+13)
|
||||||
|
#define KEY_F4 (KEY_VIRT+14)
|
||||||
|
#define KEY_F5 (KEY_VIRT+15)
|
||||||
|
#define KEY_F6 (KEY_VIRT+16)
|
||||||
|
#define KEY_F7 (KEY_VIRT+17)
|
||||||
|
#define KEY_F8 (KEY_VIRT+18)
|
||||||
|
#define KEY_F9 (KEY_VIRT+19)
|
||||||
|
#define KEY_F10 (KEY_VIRT+20)
|
||||||
|
#define KEY_F11 (KEY_VIRT+21)
|
||||||
|
#define KEY_F12 (KEY_VIRT+22)
|
||||||
|
#define KEY_F(n) (KEY_F1 + (n) - 1)
|
||||||
|
|
||||||
|
#define KEY_EVENT_BASE (0x02000000U)
|
||||||
|
#define KEY_EVENT_RESIZE (KEY_EVENT_BASE+1)
|
||||||
|
#define KEY_EVENT_AUTOTAB (KEY_EVENT_BASE+2)
|
||||||
|
#define KEY_EVENT_STOP (KEY_EVENT_BASE+3)
|
||||||
|
|
||||||
|
// Convenience
|
||||||
|
#define KEY_CTRL_UP (WITH_CTRL(KEY_UP))
|
||||||
|
#define KEY_CTRL_DOWN (WITH_CTRL(KEY_DOWN))
|
||||||
|
#define KEY_CTRL_LEFT (WITH_CTRL(KEY_LEFT))
|
||||||
|
#define KEY_CTRL_RIGHT (WITH_CTRL(KEY_RIGHT))
|
||||||
|
#define KEY_CTRL_HOME (WITH_CTRL(KEY_HOME))
|
||||||
|
#define KEY_CTRL_END (WITH_CTRL(KEY_END))
|
||||||
|
#define KEY_CTRL_DEL (WITH_CTRL(KEY_DEL))
|
||||||
|
#define KEY_CTRL_PAGEUP (WITH_CTRL(KEY_PAGEUP))
|
||||||
|
#define KEY_CTRL_PAGEDOWN (WITH_CTRL(KEY_PAGEDOWN)))
|
||||||
|
#define KEY_CTRL_INS (WITH_CTRL(KEY_INS))
|
||||||
|
|
||||||
|
#define KEY_SHIFT_TAB (WITH_SHIFT(KEY_TAB))
|
||||||
|
|
||||||
|
#endif // IC_TTY_H
|
401
deps/isocline/src/tty_esc.c
vendored
Normal file
401
deps/isocline/src/tty_esc.c
vendored
Normal file
@ -0,0 +1,401 @@
|
|||||||
|
/* ----------------------------------------------------------------------------
|
||||||
|
Copyright (c) 2021, Daan Leijen
|
||||||
|
This is free software; you can redistribute it and/or modify it
|
||||||
|
under the terms of the MIT License. A copy of the license can be
|
||||||
|
found in the "LICENSE" file at the root of this distribution.
|
||||||
|
-----------------------------------------------------------------------------*/
|
||||||
|
#include <string.h>
|
||||||
|
#include "tty.h"
|
||||||
|
|
||||||
|
/*-------------------------------------------------------------
|
||||||
|
Decoding escape sequences to key codes.
|
||||||
|
This is a bit tricky there are many variants to encode keys as escape sequences, see for example:
|
||||||
|
- <http://www.leonerd.org.uk/hacks/fixterms/>.
|
||||||
|
- <https://en.wikipedia.org/wiki/ANSI_escape_code#CSI_(Control_Sequence_Introducer)_sequences>
|
||||||
|
- <https://www.xfree86.org/current/ctlseqs.html>
|
||||||
|
- <https://vt100.net/docs/vt220-rm/contents.html>
|
||||||
|
- <https://www.ecma-international.org/wp-content/uploads/ECMA-48_5th_edition_june_1991.pdf>
|
||||||
|
|
||||||
|
Generally, for our purposes we accept a subset of escape sequences as:
|
||||||
|
|
||||||
|
escseq ::= ESC
|
||||||
|
| ESC char
|
||||||
|
| ESC start special? (number (';' modifiers)?)? final
|
||||||
|
|
||||||
|
where:
|
||||||
|
char ::= [\x00-\xFF] # any character
|
||||||
|
special ::= [:<=>?]
|
||||||
|
number ::= [0-9+]
|
||||||
|
modifiers ::= [1-9]
|
||||||
|
intermediate ::= [\x20-\x2F] # !"#$%&'()*+,-./
|
||||||
|
final ::= [\x40-\x7F] # @A–Z[\]^_`a–z{|}~
|
||||||
|
ESC ::= '\x1B'
|
||||||
|
CSI ::= ESC '['
|
||||||
|
SS3 ::= ESC 'O'
|
||||||
|
|
||||||
|
In ECMA48 `special? (number (';' modifiers)?)?` is the more liberal `[\x30-\x3F]*`
|
||||||
|
but that seems never used for key codes. If the number (vtcode or unicode) or the
|
||||||
|
modifiers are not given, we assume these are '1'.
|
||||||
|
We then accept the following key sequences:
|
||||||
|
|
||||||
|
key ::= ESC # lone ESC
|
||||||
|
| ESC char # Alt+char
|
||||||
|
| ESC '[' special? vtcode ';' modifiers '~' # vt100 codes
|
||||||
|
| ESC '[' special? '1' ';' modifiers [A-Z] # xterm codes
|
||||||
|
| ESC 'O' special? '1' ';' modifiers [A-Za-z] # SS3 codes
|
||||||
|
| ESC '[' special? unicode ';' modifiers 'u' # direct unicode code
|
||||||
|
|
||||||
|
Moreover, we translate the following special cases that do not fit into the above grammar.
|
||||||
|
First we translate away special starter sequences:
|
||||||
|
---------------------------------------------------------------------
|
||||||
|
ESC '[' '[' .. ~> ESC '[' .. # Linux sometimes uses extra '[' for CSI
|
||||||
|
ESC '[' 'O' .. ~> ESC 'O' .. # Linux sometimes uses extra '[' for SS3
|
||||||
|
ESC 'o' .. ~> ESC 'O' .. # Eterm: ctrl + SS3
|
||||||
|
ESC '?' .. ~> ESC 'O' .. # vt52 treated as SS3
|
||||||
|
|
||||||
|
And then translate the following special cases into a standard form:
|
||||||
|
---------------------------------------------------------------------
|
||||||
|
ESC '[' .. '@' ~> ESC '[' '3' '~' # Del on Mach
|
||||||
|
ESC '[' .. '9' ~> ESC '[' '2' '~' # Ins on Mach
|
||||||
|
ESC .. [^@$] ~> ESC .. '~' # ETerm,xrvt,urxt: ^ = ctrl, $ = shift, @ = alt
|
||||||
|
ESC '[' [a-d] ~> ESC '[' '1' ';' '2' [A-D] # Eterm shift+<cursor>
|
||||||
|
ESC 'O' [1-9] final ~> ESC 'O' '1' ';' [1-9] final # modifiers as parameter 1 (like on Haiku)
|
||||||
|
ESC '[' [1-9] [^~u] ~> ESC 'O' '1' ';' [1-9] final # modifiers as parameter 1
|
||||||
|
|
||||||
|
The modifier keys are encoded as "(modifiers-1) & mask" where the
|
||||||
|
shift mask is 0x01, alt 0x02 and ctrl 0x04. Therefore:
|
||||||
|
------------------------------------------------------------
|
||||||
|
1: - 5: ctrl 9: alt (for minicom)
|
||||||
|
2: shift 6: shift+ctrl
|
||||||
|
3: alt 7: alt+ctrl
|
||||||
|
4: shift+alt 8: shift+alt+ctrl
|
||||||
|
|
||||||
|
The different encodings fox vt100, xterm, and SS3 are:
|
||||||
|
|
||||||
|
vt100: ESC [ vtcode ';' modifiers '~'
|
||||||
|
--------------------------------------
|
||||||
|
1: Home 10-15: F1-F5
|
||||||
|
2: Ins 16 : F5
|
||||||
|
3: Del 17-21: F6-F10
|
||||||
|
4: End 23-26: F11-F14
|
||||||
|
5: PageUp 28 : F15
|
||||||
|
6: PageDn 29 : F16
|
||||||
|
7: Home 31-34: F17-F20
|
||||||
|
8: End
|
||||||
|
|
||||||
|
xterm: ESC [ 1 ';' modifiers [A-Z]
|
||||||
|
-----------------------------------
|
||||||
|
A: Up N: F2
|
||||||
|
B: Down O: F3
|
||||||
|
C: Right P: F4
|
||||||
|
D: Left Q: F5
|
||||||
|
E: '5' R: F6
|
||||||
|
F: End S: F7
|
||||||
|
G: T: F8
|
||||||
|
H: Home U: PageDn
|
||||||
|
I: PageUp V: PageUp
|
||||||
|
J: W: F11
|
||||||
|
K: X: F12
|
||||||
|
L: Ins Y: End
|
||||||
|
M: F1 Z: shift+Tab
|
||||||
|
|
||||||
|
SS3: ESC 'O' 1 ';' modifiers [A-Za-z]
|
||||||
|
---------------------------------------
|
||||||
|
(normal) (numpad)
|
||||||
|
A: Up N: a: Up n:
|
||||||
|
B: Down O: b: Down o:
|
||||||
|
C: Right P: F1 c: Right p: Ins
|
||||||
|
D: Left Q: F2 d: Left q: End
|
||||||
|
E: '5' R: F3 e: r: Down
|
||||||
|
F: End S: F4 f: s: PageDn
|
||||||
|
G: T: F5 g: t: Left
|
||||||
|
H: Home U: F6 h: u: '5'
|
||||||
|
I: Tab V: F7 i: v: Right
|
||||||
|
J: W: F8 j: '*' w: Home
|
||||||
|
K: X: F9 k: '+' x: Up
|
||||||
|
L: Y: F10 l: ',' y: PageUp
|
||||||
|
M: \x0A '\n' Z: shift+Tab m: '-' z:
|
||||||
|
|
||||||
|
-------------------------------------------------------------*/
|
||||||
|
|
||||||
|
//-------------------------------------------------------------
|
||||||
|
// Decode escape sequences
|
||||||
|
//-------------------------------------------------------------
|
||||||
|
|
||||||
|
static code_t esc_decode_vt(uint32_t vt_code ) {
|
||||||
|
switch(vt_code) {
|
||||||
|
case 1: return KEY_HOME;
|
||||||
|
case 2: return KEY_INS;
|
||||||
|
case 3: return KEY_DEL;
|
||||||
|
case 4: return KEY_END;
|
||||||
|
case 5: return KEY_PAGEUP;
|
||||||
|
case 6: return KEY_PAGEDOWN;
|
||||||
|
case 7: return KEY_HOME;
|
||||||
|
case 8: return KEY_END;
|
||||||
|
default:
|
||||||
|
if (vt_code >= 10 && vt_code <= 15) return KEY_F(1 + (vt_code - 10));
|
||||||
|
if (vt_code == 16) return KEY_F5; // minicom
|
||||||
|
if (vt_code >= 17 && vt_code <= 21) return KEY_F(6 + (vt_code - 17));
|
||||||
|
if (vt_code >= 23 && vt_code <= 26) return KEY_F(11 + (vt_code - 23));
|
||||||
|
if (vt_code >= 28 && vt_code <= 29) return KEY_F(15 + (vt_code - 28));
|
||||||
|
if (vt_code >= 31 && vt_code <= 34) return KEY_F(17 + (vt_code - 31));
|
||||||
|
}
|
||||||
|
return KEY_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static code_t esc_decode_xterm( uint8_t xcode ) {
|
||||||
|
// ESC [
|
||||||
|
switch(xcode) {
|
||||||
|
case 'A': return KEY_UP;
|
||||||
|
case 'B': return KEY_DOWN;
|
||||||
|
case 'C': return KEY_RIGHT;
|
||||||
|
case 'D': return KEY_LEFT;
|
||||||
|
case 'E': return '5'; // numpad 5
|
||||||
|
case 'F': return KEY_END;
|
||||||
|
case 'H': return KEY_HOME;
|
||||||
|
case 'Z': return KEY_TAB | KEY_MOD_SHIFT;
|
||||||
|
// Freebsd:
|
||||||
|
case 'I': return KEY_PAGEUP;
|
||||||
|
case 'L': return KEY_INS;
|
||||||
|
case 'M': return KEY_F1;
|
||||||
|
case 'N': return KEY_F2;
|
||||||
|
case 'O': return KEY_F3;
|
||||||
|
case 'P': return KEY_F4; // note: differs from <https://en.wikipedia.org/wiki/ANSI_escape_code#CSI_(Control_Sequence_Introducer)_sequences>
|
||||||
|
case 'Q': return KEY_F5;
|
||||||
|
case 'R': return KEY_F6;
|
||||||
|
case 'S': return KEY_F7;
|
||||||
|
case 'T': return KEY_F8;
|
||||||
|
case 'U': return KEY_PAGEDOWN; // Mach
|
||||||
|
case 'V': return KEY_PAGEUP; // Mach
|
||||||
|
case 'W': return KEY_F11;
|
||||||
|
case 'X': return KEY_F12;
|
||||||
|
case 'Y': return KEY_END; // Mach
|
||||||
|
}
|
||||||
|
return KEY_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static code_t esc_decode_ss3( uint8_t ss3_code ) {
|
||||||
|
// ESC O
|
||||||
|
switch(ss3_code) {
|
||||||
|
case 'A': return KEY_UP;
|
||||||
|
case 'B': return KEY_DOWN;
|
||||||
|
case 'C': return KEY_RIGHT;
|
||||||
|
case 'D': return KEY_LEFT;
|
||||||
|
case 'E': return '5'; // numpad 5
|
||||||
|
case 'F': return KEY_END;
|
||||||
|
case 'H': return KEY_HOME;
|
||||||
|
case 'I': return KEY_TAB;
|
||||||
|
case 'Z': return KEY_TAB | KEY_MOD_SHIFT;
|
||||||
|
case 'M': return KEY_LINEFEED;
|
||||||
|
case 'P': return KEY_F1;
|
||||||
|
case 'Q': return KEY_F2;
|
||||||
|
case 'R': return KEY_F3;
|
||||||
|
case 'S': return KEY_F4;
|
||||||
|
// on Mach
|
||||||
|
case 'T': return KEY_F5;
|
||||||
|
case 'U': return KEY_F6;
|
||||||
|
case 'V': return KEY_F7;
|
||||||
|
case 'W': return KEY_F8;
|
||||||
|
case 'X': return KEY_F9; // '=' on vt220
|
||||||
|
case 'Y': return KEY_F10;
|
||||||
|
// numpad
|
||||||
|
case 'a': return KEY_UP;
|
||||||
|
case 'b': return KEY_DOWN;
|
||||||
|
case 'c': return KEY_RIGHT;
|
||||||
|
case 'd': return KEY_LEFT;
|
||||||
|
case 'j': return '*';
|
||||||
|
case 'k': return '+';
|
||||||
|
case 'l': return ',';
|
||||||
|
case 'm': return '-';
|
||||||
|
case 'n': return KEY_DEL; // '.'
|
||||||
|
case 'o': return '/';
|
||||||
|
case 'p': return KEY_INS;
|
||||||
|
case 'q': return KEY_END;
|
||||||
|
case 'r': return KEY_DOWN;
|
||||||
|
case 's': return KEY_PAGEDOWN;
|
||||||
|
case 't': return KEY_LEFT;
|
||||||
|
case 'u': return '5';
|
||||||
|
case 'v': return KEY_RIGHT;
|
||||||
|
case 'w': return KEY_HOME;
|
||||||
|
case 'x': return KEY_UP;
|
||||||
|
case 'y': return KEY_PAGEUP;
|
||||||
|
}
|
||||||
|
return KEY_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void tty_read_csi_num(tty_t* tty, uint8_t* ppeek, uint32_t* num, long esc_timeout) {
|
||||||
|
*num = 1; // default
|
||||||
|
ssize_t count = 0;
|
||||||
|
uint32_t i = 0;
|
||||||
|
while (*ppeek >= '0' && *ppeek <= '9' && count < 16) {
|
||||||
|
uint8_t digit = *ppeek - '0';
|
||||||
|
if (!tty_readc_noblock(tty,ppeek,esc_timeout)) break; // peek is not modified in this case
|
||||||
|
count++;
|
||||||
|
i = 10*i + digit;
|
||||||
|
}
|
||||||
|
if (count > 0) *num = i;
|
||||||
|
}
|
||||||
|
|
||||||
|
static code_t tty_read_csi(tty_t* tty, uint8_t c1, uint8_t peek, code_t mods0, long esc_timeout) {
|
||||||
|
// CSI starts with 0x9b (c1=='[') | ESC [ (c1=='[') | ESC [Oo?] (c1 == 'O') /* = SS3 */
|
||||||
|
|
||||||
|
// check for extra starter '[' (Linux sends ESC [ [ 15 ~ for F5 for example)
|
||||||
|
if (c1 == '[' && strchr("[Oo", (char)peek) != NULL) {
|
||||||
|
uint8_t cx = peek;
|
||||||
|
if (tty_readc_noblock(tty,&peek,esc_timeout)) {
|
||||||
|
c1 = cx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// "special" characters ('?' is used for private sequences)
|
||||||
|
uint8_t special = 0;
|
||||||
|
if (strchr(":<=>?",(char)peek) != NULL) {
|
||||||
|
special = peek;
|
||||||
|
if (!tty_readc_noblock(tty,&peek,esc_timeout)) {
|
||||||
|
tty_cpush_char(tty,special); // recover
|
||||||
|
return (key_unicode(c1) | KEY_MOD_ALT); // Alt+<anychar>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// up to 2 parameters that default to 1
|
||||||
|
uint32_t num1 = 1;
|
||||||
|
uint32_t num2 = 1;
|
||||||
|
tty_read_csi_num(tty,&peek,&num1,esc_timeout);
|
||||||
|
if (peek == ';') {
|
||||||
|
if (!tty_readc_noblock(tty,&peek,esc_timeout)) return KEY_NONE;
|
||||||
|
tty_read_csi_num(tty,&peek,&num2,esc_timeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
// the final character (we do not allow 'intermediate characters')
|
||||||
|
uint8_t final = peek;
|
||||||
|
code_t modifiers = mods0;
|
||||||
|
|
||||||
|
debug_msg("tty: escape sequence: ESC %c %c %d;%d %c\n", c1, (special == 0 ? '_' : special), num1, num2, final);
|
||||||
|
|
||||||
|
// Adjust special cases into standard ones.
|
||||||
|
if ((final == '@' || final == '9') && c1 == '[' && num1 == 1) {
|
||||||
|
// ESC [ @, ESC [ 9 : on Mach
|
||||||
|
if (final == '@') num1 = 3; // DEL
|
||||||
|
else if (final == '9') num1 = 2; // INS
|
||||||
|
final = '~';
|
||||||
|
}
|
||||||
|
else if (final == '^' || final == '$' || final == '@') {
|
||||||
|
// Eterm/rxvt/urxt
|
||||||
|
if (final=='^') modifiers |= KEY_MOD_CTRL;
|
||||||
|
if (final=='$') modifiers |= KEY_MOD_SHIFT;
|
||||||
|
if (final=='@') modifiers |= KEY_MOD_SHIFT | KEY_MOD_CTRL;
|
||||||
|
final = '~';
|
||||||
|
}
|
||||||
|
else if (c1 == '[' && final >= 'a' && final <= 'd') { // note: do not catch ESC [ .. u (for unicode)
|
||||||
|
// ESC [ [a-d] : on Eterm for shift+ cursor
|
||||||
|
modifiers |= KEY_MOD_SHIFT;
|
||||||
|
final = 'A' + (final - 'a');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (((c1 == 'O') || (c1=='[' && final != '~' && final != 'u')) &&
|
||||||
|
(num2 == 1 && num1 > 1 && num1 <= 8))
|
||||||
|
{
|
||||||
|
// on haiku the modifier can be parameter 1, make it parameter 2 instead
|
||||||
|
num2 = num1;
|
||||||
|
num1 = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// parameter 2 determines the modifiers
|
||||||
|
if (num2 > 1 && num2 <= 9) {
|
||||||
|
if (num2 == 9) num2 = 3; // iTerm2 in xterm mode
|
||||||
|
num2--;
|
||||||
|
if (num2 & 0x1) modifiers |= KEY_MOD_SHIFT;
|
||||||
|
if (num2 & 0x2) modifiers |= KEY_MOD_ALT;
|
||||||
|
if (num2 & 0x4) modifiers |= KEY_MOD_CTRL;
|
||||||
|
}
|
||||||
|
|
||||||
|
// and translate
|
||||||
|
code_t code = KEY_NONE;
|
||||||
|
if (final == '~') {
|
||||||
|
// vt codes
|
||||||
|
code = esc_decode_vt(num1);
|
||||||
|
}
|
||||||
|
else if (c1 == '[' && final == 'u') {
|
||||||
|
// unicode
|
||||||
|
code = key_unicode(num1);
|
||||||
|
}
|
||||||
|
else if (c1 == 'O' && ((final >= 'A' && final <= 'Z') || (final >= 'a' && final <= 'z'))) {
|
||||||
|
// ss3
|
||||||
|
code = esc_decode_ss3(final);
|
||||||
|
}
|
||||||
|
else if (num1 == 1 && final >= 'A' && final <= 'Z') {
|
||||||
|
// xterm
|
||||||
|
code = esc_decode_xterm(final);
|
||||||
|
}
|
||||||
|
else if (c1 == '[' && final == 'R') {
|
||||||
|
// cursor position
|
||||||
|
code = KEY_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (code == KEY_NONE && final != 'R') {
|
||||||
|
debug_msg("tty: ignore escape sequence: ESC %c %zu;%zu %c\n", c1, num1, num2, final);
|
||||||
|
}
|
||||||
|
return (code != KEY_NONE ? (code | modifiers) : KEY_NONE);
|
||||||
|
}
|
||||||
|
|
||||||
|
static code_t tty_read_osc( tty_t* tty, uint8_t* ppeek, long esc_timeout ) {
|
||||||
|
debug_msg("discard OSC response..\n");
|
||||||
|
// keep reading until termination: OSC is terminated by BELL, or ESC \ (ST) (and STX)
|
||||||
|
while (true) {
|
||||||
|
uint8_t c = *ppeek;
|
||||||
|
if (c <= '\x07') { // BELL and anything below (STX, ^C, ^D)
|
||||||
|
if (c != '\x07') { tty_cpush_char( tty, c ); }
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else if (c=='\x1B') {
|
||||||
|
uint8_t c1;
|
||||||
|
if (!tty_readc_noblock(tty, &c1, esc_timeout)) break;
|
||||||
|
if (c1=='\\') break;
|
||||||
|
tty_cpush_char(tty,c1);
|
||||||
|
}
|
||||||
|
if (!tty_readc_noblock(tty, ppeek, esc_timeout)) break;
|
||||||
|
}
|
||||||
|
return KEY_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
ic_private code_t tty_read_esc(tty_t* tty, long esc_initial_timeout, long esc_timeout) {
|
||||||
|
code_t mods = 0;
|
||||||
|
uint8_t peek = 0;
|
||||||
|
|
||||||
|
// lone ESC?
|
||||||
|
if (!tty_readc_noblock(tty, &peek, esc_initial_timeout)) return KEY_ESC;
|
||||||
|
|
||||||
|
// treat ESC ESC as Alt modifier (macOS sends ESC ESC [ [A-D] for alt-<cursor>)
|
||||||
|
if (peek == KEY_ESC) {
|
||||||
|
if (!tty_readc_noblock(tty, &peek, esc_timeout)) goto alt;
|
||||||
|
mods |= KEY_MOD_ALT;
|
||||||
|
}
|
||||||
|
|
||||||
|
// CSI ?
|
||||||
|
if (peek == '[') {
|
||||||
|
if (!tty_readc_noblock(tty, &peek, esc_timeout)) goto alt;
|
||||||
|
return tty_read_csi(tty, '[', peek, mods, esc_timeout); // ESC [ ...
|
||||||
|
}
|
||||||
|
|
||||||
|
// SS3?
|
||||||
|
if (peek == 'O' || peek == 'o' || peek == '?' /*vt52*/) {
|
||||||
|
uint8_t c1 = peek;
|
||||||
|
if (!tty_readc_noblock(tty, &peek, esc_timeout)) goto alt;
|
||||||
|
if (c1 == 'o') {
|
||||||
|
// ETerm uses this for ctrl+<cursor>
|
||||||
|
mods |= KEY_MOD_CTRL;
|
||||||
|
}
|
||||||
|
// treat all as standard SS3 'O'
|
||||||
|
return tty_read_csi(tty,'O',peek,mods, esc_timeout); // ESC [Oo?] ...
|
||||||
|
}
|
||||||
|
|
||||||
|
// OSC: we may get a delayed query response; ensure it is ignored
|
||||||
|
if (peek == ']') {
|
||||||
|
if (!tty_readc_noblock(tty, &peek, esc_timeout)) goto alt;
|
||||||
|
return tty_read_osc(tty, &peek, esc_timeout); // ESC ] ...
|
||||||
|
}
|
||||||
|
|
||||||
|
alt:
|
||||||
|
// Alt+<char>
|
||||||
|
return (key_unicode(peek) | KEY_MOD_ALT); // ESC <anychar>
|
||||||
|
}
|
67
deps/isocline/src/undo.c
vendored
Normal file
67
deps/isocline/src/undo.c
vendored
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
/* ----------------------------------------------------------------------------
|
||||||
|
Copyright (c) 2021, Daan Leijen
|
||||||
|
This is free software; you can redistribute it and/or modify it
|
||||||
|
under the terms of the MIT License. A copy of the license can be
|
||||||
|
found in the "LICENSE" file at the root of this distribution.
|
||||||
|
-----------------------------------------------------------------------------*/
|
||||||
|
#include <string.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
#include "../include/isocline.h"
|
||||||
|
#include "common.h"
|
||||||
|
#include "env.h"
|
||||||
|
#include "stringbuf.h"
|
||||||
|
#include "completions.h"
|
||||||
|
#include "undo.h"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//-------------------------------------------------------------
|
||||||
|
// edit state
|
||||||
|
//-------------------------------------------------------------
|
||||||
|
struct editstate_s {
|
||||||
|
struct editstate_s* next;
|
||||||
|
const char* input; // input
|
||||||
|
ssize_t pos; // cursor position
|
||||||
|
};
|
||||||
|
|
||||||
|
ic_private void editstate_init( editstate_t** es ) {
|
||||||
|
*es = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
ic_private void editstate_done( alloc_t* mem, editstate_t** es ) {
|
||||||
|
while (*es != NULL) {
|
||||||
|
editstate_t* next = (*es)->next;
|
||||||
|
mem_free(mem, (*es)->input);
|
||||||
|
mem_free(mem, *es );
|
||||||
|
*es = next;
|
||||||
|
}
|
||||||
|
*es = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
ic_private void editstate_capture( alloc_t* mem, editstate_t** es, const char* input, ssize_t pos) {
|
||||||
|
if (input==NULL) input = "";
|
||||||
|
// alloc
|
||||||
|
editstate_t* entry = mem_zalloc_tp(mem, editstate_t);
|
||||||
|
if (entry == NULL) return;
|
||||||
|
// initialize
|
||||||
|
entry->input = mem_strdup( mem, input);
|
||||||
|
entry->pos = pos;
|
||||||
|
if (entry->input == NULL) { mem_free(mem, entry); return; }
|
||||||
|
// and push
|
||||||
|
entry->next = *es;
|
||||||
|
*es = entry;
|
||||||
|
}
|
||||||
|
|
||||||
|
// caller should free *input
|
||||||
|
ic_private bool editstate_restore( alloc_t* mem, editstate_t** es, const char** input, ssize_t* pos ) {
|
||||||
|
if (*es == NULL) return false;
|
||||||
|
// pop
|
||||||
|
editstate_t* entry = *es;
|
||||||
|
*es = entry->next;
|
||||||
|
*input = entry->input;
|
||||||
|
*pos = entry->pos;
|
||||||
|
mem_free(mem, entry);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
24
deps/isocline/src/undo.h
vendored
Normal file
24
deps/isocline/src/undo.h
vendored
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
/* ----------------------------------------------------------------------------
|
||||||
|
Copyright (c) 2021, Daan Leijen
|
||||||
|
This is free software; you can redistribute it and/or modify it
|
||||||
|
under the terms of the MIT License. A copy of the license can be
|
||||||
|
found in the "LICENSE" file at the root of this distribution.
|
||||||
|
-----------------------------------------------------------------------------*/
|
||||||
|
#pragma once
|
||||||
|
#ifndef IC_UNDO_H
|
||||||
|
#define IC_UNDO_H
|
||||||
|
|
||||||
|
#include "common.h"
|
||||||
|
|
||||||
|
//-------------------------------------------------------------
|
||||||
|
// Edit state
|
||||||
|
//-------------------------------------------------------------
|
||||||
|
struct editstate_s;
|
||||||
|
typedef struct editstate_s editstate_t;
|
||||||
|
|
||||||
|
ic_private void editstate_init( editstate_t** es );
|
||||||
|
ic_private void editstate_done( alloc_t* mem, editstate_t** es );
|
||||||
|
ic_private void editstate_capture( alloc_t* mem, editstate_t** es, const char* input, ssize_t pos);
|
||||||
|
ic_private bool editstate_restore( alloc_t* mem, editstate_t** es, const char** input, ssize_t* pos ); // caller needs to free input
|
||||||
|
|
||||||
|
#endif // IC_UNDO_H
|
292
deps/isocline/src/wcwidth.c
vendored
Normal file
292
deps/isocline/src/wcwidth.c
vendored
Normal file
@ -0,0 +1,292 @@
|
|||||||
|
// include in "stringbuf.c"
|
||||||
|
/*
|
||||||
|
* This is an implementation of wcwidth() and wcswidth() (defined in
|
||||||
|
* IEEE Std 1002.1-2001) for Unicode.
|
||||||
|
*
|
||||||
|
* http://www.opengroup.org/onlinepubs/007904975/functions/wcwidth.html
|
||||||
|
* http://www.opengroup.org/onlinepubs/007904975/functions/wcswidth.html
|
||||||
|
*
|
||||||
|
* In fixed-width output devices, Latin characters all occupy a single
|
||||||
|
* "cell" position of equal width, whereas ideographic CJK characters
|
||||||
|
* occupy two such cells. Interoperability between terminal-line
|
||||||
|
* applications and (teletype-style) character terminals using the
|
||||||
|
* UTF-8 encoding requires agreement on which character should advance
|
||||||
|
* the cursor by how many cell positions. No established formal
|
||||||
|
* standards exist at present on which Unicode character shall occupy
|
||||||
|
* how many cell positions on character terminals. These routines are
|
||||||
|
* a first attempt of defining such behavior based on simple rules
|
||||||
|
* applied to data provided by the Unicode Consortium.
|
||||||
|
*
|
||||||
|
* For some graphical characters, the Unicode standard explicitly
|
||||||
|
* defines a character-cell width via the definition of the East Asian
|
||||||
|
* FullWidth (F), Wide (W), Half-width (H), and Narrow (Na) classes.
|
||||||
|
* In all these cases, there is no ambiguity about which width a
|
||||||
|
* terminal shall use. For characters in the East Asian Ambiguous (A)
|
||||||
|
* class, the width choice depends purely on a preference of backward
|
||||||
|
* compatibility with either historic CJK or Western practice.
|
||||||
|
* Choosing single-width for these characters is easy to justify as
|
||||||
|
* the appropriate long-term solution, as the CJK practice of
|
||||||
|
* displaying these characters as double-width comes from historic
|
||||||
|
* implementation simplicity (8-bit encoded characters were displayed
|
||||||
|
* single-width and 16-bit ones double-width, even for Greek,
|
||||||
|
* Cyrillic, etc.) and not any typographic considerations.
|
||||||
|
*
|
||||||
|
* Much less clear is the choice of width for the Not East Asian
|
||||||
|
* (Neutral) class. Existing practice does not dictate a width for any
|
||||||
|
* of these characters. It would nevertheless make sense
|
||||||
|
* typographically to allocate two character cells to characters such
|
||||||
|
* as for instance EM SPACE or VOLUME INTEGRAL, which cannot be
|
||||||
|
* represented adequately with a single-width glyph. The following
|
||||||
|
* routines at present merely assign a single-cell width to all
|
||||||
|
* neutral characters, in the interest of simplicity. This is not
|
||||||
|
* entirely satisfactory and should be reconsidered before
|
||||||
|
* establishing a formal standard in this area. At the moment, the
|
||||||
|
* decision which Not East Asian (Neutral) characters should be
|
||||||
|
* represented by double-width glyphs cannot yet be answered by
|
||||||
|
* applying a simple rule from the Unicode database content. Setting
|
||||||
|
* up a proper standard for the behavior of UTF-8 character terminals
|
||||||
|
* will require a careful analysis not only of each Unicode character,
|
||||||
|
* but also of each presentation form, something the author of these
|
||||||
|
* routines has avoided to do so far.
|
||||||
|
*
|
||||||
|
* http://www.unicode.org/unicode/reports/tr11/
|
||||||
|
*
|
||||||
|
* Markus Kuhn -- 2007-05-26 (Unicode 5.0)
|
||||||
|
*
|
||||||
|
* Permission to use, copy, modify, and distribute this software
|
||||||
|
* for any purpose and without fee is hereby granted. The author
|
||||||
|
* disclaims all warranties with regard to this software.
|
||||||
|
*
|
||||||
|
* Latest version: http://www.cl.cam.ac.uk/~mgk25/ucs/wcwidth.c
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
struct interval {
|
||||||
|
int32_t first;
|
||||||
|
int32_t last;
|
||||||
|
};
|
||||||
|
|
||||||
|
/* auxiliary function for binary search in interval table */
|
||||||
|
static int bisearch(int32_t ucs, const struct interval *table, int max) {
|
||||||
|
int min = 0;
|
||||||
|
int mid;
|
||||||
|
|
||||||
|
if (ucs < table[0].first || ucs > table[max].last)
|
||||||
|
return 0;
|
||||||
|
while (max >= min) {
|
||||||
|
mid = (min + max) / 2;
|
||||||
|
if (ucs > table[mid].last)
|
||||||
|
min = mid + 1;
|
||||||
|
else if (ucs < table[mid].first)
|
||||||
|
max = mid - 1;
|
||||||
|
else
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* The following two functions define the column width of an ISO 10646
|
||||||
|
* character as follows:
|
||||||
|
*
|
||||||
|
* - The null character (U+0000) has a column width of 0.
|
||||||
|
*
|
||||||
|
* - Other C0/C1 control characters and DEL will lead to a return
|
||||||
|
* value of -1.
|
||||||
|
*
|
||||||
|
* - Non-spacing and enclosing combining characters (general
|
||||||
|
* category code Mn or Me in the Unicode database) have a
|
||||||
|
* column width of 0.
|
||||||
|
*
|
||||||
|
* - SOFT HYPHEN (U+00AD) has a column width of 1.
|
||||||
|
*
|
||||||
|
* - Other format characters (general category code Cf in the Unicode
|
||||||
|
* database) and ZERO WIDTH SPACE (U+200B) have a column width of 0.
|
||||||
|
*
|
||||||
|
* - Hangul Jamo medial vowels and final consonants (U+1160-U+11FF)
|
||||||
|
* have a column width of 0.
|
||||||
|
*
|
||||||
|
* - Spacing characters in the East Asian Wide (W) or East Asian
|
||||||
|
* Full-width (F) category as defined in Unicode Technical
|
||||||
|
* Report #11 have a column width of 2.
|
||||||
|
*
|
||||||
|
* - All remaining characters (including all printable
|
||||||
|
* ISO 8859-1 and WGL4 characters, Unicode control characters,
|
||||||
|
* etc.) have a column width of 1.
|
||||||
|
*
|
||||||
|
* This implementation assumes that wchar_t characters are encoded
|
||||||
|
* in ISO 10646.
|
||||||
|
*/
|
||||||
|
|
||||||
|
static int mk_is_wide_char(int32_t ucs) {
|
||||||
|
static const struct interval wide[] = {
|
||||||
|
{0x1100, 0x115f}, {0x231a, 0x231b}, {0x2329, 0x232a},
|
||||||
|
{0x23e9, 0x23ec}, {0x23f0, 0x23f0}, {0x23f3, 0x23f3},
|
||||||
|
{0x25fd, 0x25fe}, {0x2614, 0x2615}, {0x2648, 0x2653},
|
||||||
|
{0x267f, 0x267f}, {0x2693, 0x2693}, {0x26a1, 0x26a1},
|
||||||
|
{0x26aa, 0x26ab}, {0x26bd, 0x26be}, {0x26c4, 0x26c5},
|
||||||
|
{0x26ce, 0x26ce}, {0x26d4, 0x26d4}, {0x26ea, 0x26ea},
|
||||||
|
{0x26f2, 0x26f3}, {0x26f5, 0x26f5}, {0x26fa, 0x26fa},
|
||||||
|
{0x26fd, 0x26fd}, {0x2705, 0x2705}, {0x270a, 0x270b},
|
||||||
|
{0x2728, 0x2728}, {0x274c, 0x274c}, {0x274e, 0x274e},
|
||||||
|
{0x2753, 0x2755}, {0x2757, 0x2757}, {0x2795, 0x2797},
|
||||||
|
{0x27b0, 0x27b0}, {0x27bf, 0x27bf}, {0x2b1b, 0x2b1c},
|
||||||
|
{0x2b50, 0x2b50}, {0x2b55, 0x2b55}, {0x2e80, 0x2fdf},
|
||||||
|
{0x2ff0, 0x303e}, {0x3040, 0x3247}, {0x3250, 0x4dbf},
|
||||||
|
{0x4e00, 0xa4cf}, {0xa960, 0xa97f}, {0xac00, 0xd7a3},
|
||||||
|
{0xf900, 0xfaff}, {0xfe10, 0xfe19}, {0xfe30, 0xfe6f},
|
||||||
|
{0xff01, 0xff60}, {0xffe0, 0xffe6}, {0x16fe0, 0x16fe1},
|
||||||
|
{0x17000, 0x18aff}, {0x1b000, 0x1b12f}, {0x1b170, 0x1b2ff},
|
||||||
|
{0x1f004, 0x1f004}, {0x1f0cf, 0x1f0cf}, {0x1f18e, 0x1f18e},
|
||||||
|
{0x1f191, 0x1f19a}, {0x1f200, 0x1f202}, {0x1f210, 0x1f23b},
|
||||||
|
{0x1f240, 0x1f248}, {0x1f250, 0x1f251}, {0x1f260, 0x1f265},
|
||||||
|
{0x1f300, 0x1f320}, {0x1f32d, 0x1f335}, {0x1f337, 0x1f37c},
|
||||||
|
{0x1f37e, 0x1f393}, {0x1f3a0, 0x1f3ca}, {0x1f3cf, 0x1f3d3},
|
||||||
|
{0x1f3e0, 0x1f3f0}, {0x1f3f4, 0x1f3f4}, {0x1f3f8, 0x1f43e},
|
||||||
|
{0x1f440, 0x1f440}, {0x1f442, 0x1f4fc}, {0x1f4ff, 0x1f53d},
|
||||||
|
{0x1f54b, 0x1f54e}, {0x1f550, 0x1f567}, {0x1f57a, 0x1f57a},
|
||||||
|
{0x1f595, 0x1f596}, {0x1f5a4, 0x1f5a4}, {0x1f5fb, 0x1f64f},
|
||||||
|
{0x1f680, 0x1f6c5}, {0x1f6cc, 0x1f6cc}, {0x1f6d0, 0x1f6d2},
|
||||||
|
{0x1f6eb, 0x1f6ec}, {0x1f6f4, 0x1f6f8}, {0x1f910, 0x1f93e},
|
||||||
|
{0x1f940, 0x1f94c}, {0x1f950, 0x1f96b}, {0x1f980, 0x1f997},
|
||||||
|
{0x1f9c0, 0x1f9c0}, {0x1f9d0, 0x1f9e6}, {0x20000, 0x2fffd},
|
||||||
|
{0x30000, 0x3fffd},
|
||||||
|
};
|
||||||
|
|
||||||
|
if ( bisearch(ucs, wide, sizeof(wide) / sizeof(struct interval) - 1) ) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int mk_wcwidth(int32_t ucs) {
|
||||||
|
/* sorted list of non-overlapping intervals of non-spacing characters */
|
||||||
|
/* generated by "uniset +cat=Me +cat=Mn +cat=Cf -00AD +1160-11FF +200B c" */
|
||||||
|
static const struct interval combining[] = {
|
||||||
|
{0x00ad, 0x00ad}, {0x0300, 0x036f}, {0x0483, 0x0489},
|
||||||
|
{0x0591, 0x05bd}, {0x05bf, 0x05bf}, {0x05c1, 0x05c2},
|
||||||
|
{0x05c4, 0x05c5}, {0x05c7, 0x05c7}, {0x0610, 0x061a},
|
||||||
|
{0x061c, 0x061c}, {0x064b, 0x065f}, {0x0670, 0x0670},
|
||||||
|
{0x06d6, 0x06dc}, {0x06df, 0x06e4}, {0x06e7, 0x06e8},
|
||||||
|
{0x06ea, 0x06ed}, {0x0711, 0x0711}, {0x0730, 0x074a},
|
||||||
|
{0x07a6, 0x07b0}, {0x07eb, 0x07f3}, {0x0816, 0x0819},
|
||||||
|
{0x081b, 0x0823}, {0x0825, 0x0827}, {0x0829, 0x082d},
|
||||||
|
{0x0859, 0x085b}, {0x08d4, 0x08e1}, {0x08e3, 0x0902},
|
||||||
|
{0x093a, 0x093a}, {0x093c, 0x093c}, {0x0941, 0x0948},
|
||||||
|
{0x094d, 0x094d}, {0x0951, 0x0957}, {0x0962, 0x0963},
|
||||||
|
{0x0981, 0x0981}, {0x09bc, 0x09bc}, {0x09c1, 0x09c4},
|
||||||
|
{0x09cd, 0x09cd}, {0x09e2, 0x09e3}, {0x0a01, 0x0a02},
|
||||||
|
{0x0a3c, 0x0a3c}, {0x0a41, 0x0a42}, {0x0a47, 0x0a48},
|
||||||
|
{0x0a4b, 0x0a4d}, {0x0a51, 0x0a51}, {0x0a70, 0x0a71},
|
||||||
|
{0x0a75, 0x0a75}, {0x0a81, 0x0a82}, {0x0abc, 0x0abc},
|
||||||
|
{0x0ac1, 0x0ac5}, {0x0ac7, 0x0ac8}, {0x0acd, 0x0acd},
|
||||||
|
{0x0ae2, 0x0ae3}, {0x0afa, 0x0aff}, {0x0b01, 0x0b01},
|
||||||
|
{0x0b3c, 0x0b3c}, {0x0b3f, 0x0b3f}, {0x0b41, 0x0b44},
|
||||||
|
{0x0b4d, 0x0b4d}, {0x0b56, 0x0b56}, {0x0b62, 0x0b63},
|
||||||
|
{0x0b82, 0x0b82}, {0x0bc0, 0x0bc0}, {0x0bcd, 0x0bcd},
|
||||||
|
{0x0c00, 0x0c00}, {0x0c3e, 0x0c40}, {0x0c46, 0x0c48},
|
||||||
|
{0x0c4a, 0x0c4d}, {0x0c55, 0x0c56}, {0x0c62, 0x0c63},
|
||||||
|
{0x0c81, 0x0c81}, {0x0cbc, 0x0cbc}, {0x0cbf, 0x0cbf},
|
||||||
|
{0x0cc6, 0x0cc6}, {0x0ccc, 0x0ccd}, {0x0ce2, 0x0ce3},
|
||||||
|
{0x0d00, 0x0d01}, {0x0d3b, 0x0d3c}, {0x0d41, 0x0d44},
|
||||||
|
{0x0d4d, 0x0d4d}, {0x0d62, 0x0d63}, {0x0dca, 0x0dca},
|
||||||
|
{0x0dd2, 0x0dd4}, {0x0dd6, 0x0dd6}, {0x0e31, 0x0e31},
|
||||||
|
{0x0e34, 0x0e3a}, {0x0e47, 0x0e4e}, {0x0eb1, 0x0eb1},
|
||||||
|
{0x0eb4, 0x0eb9}, {0x0ebb, 0x0ebc}, {0x0ec8, 0x0ecd},
|
||||||
|
{0x0f18, 0x0f19}, {0x0f35, 0x0f35}, {0x0f37, 0x0f37},
|
||||||
|
{0x0f39, 0x0f39}, {0x0f71, 0x0f7e}, {0x0f80, 0x0f84},
|
||||||
|
{0x0f86, 0x0f87}, {0x0f8d, 0x0f97}, {0x0f99, 0x0fbc},
|
||||||
|
{0x0fc6, 0x0fc6}, {0x102d, 0x1030}, {0x1032, 0x1037},
|
||||||
|
{0x1039, 0x103a}, {0x103d, 0x103e}, {0x1058, 0x1059},
|
||||||
|
{0x105e, 0x1060}, {0x1071, 0x1074}, {0x1082, 0x1082},
|
||||||
|
{0x1085, 0x1086}, {0x108d, 0x108d}, {0x109d, 0x109d},
|
||||||
|
{0x1160, 0x11ff}, {0x135d, 0x135f}, {0x1712, 0x1714},
|
||||||
|
{0x1732, 0x1734}, {0x1752, 0x1753}, {0x1772, 0x1773},
|
||||||
|
{0x17b4, 0x17b5}, {0x17b7, 0x17bd}, {0x17c6, 0x17c6},
|
||||||
|
{0x17c9, 0x17d3}, {0x17dd, 0x17dd}, {0x180b, 0x180e},
|
||||||
|
{0x1885, 0x1886}, {0x18a9, 0x18a9}, {0x1920, 0x1922},
|
||||||
|
{0x1927, 0x1928}, {0x1932, 0x1932}, {0x1939, 0x193b},
|
||||||
|
{0x1a17, 0x1a18}, {0x1a1b, 0x1a1b}, {0x1a56, 0x1a56},
|
||||||
|
{0x1a58, 0x1a5e}, {0x1a60, 0x1a60}, {0x1a62, 0x1a62},
|
||||||
|
{0x1a65, 0x1a6c}, {0x1a73, 0x1a7c}, {0x1a7f, 0x1a7f},
|
||||||
|
{0x1ab0, 0x1abe}, {0x1b00, 0x1b03}, {0x1b34, 0x1b34},
|
||||||
|
{0x1b36, 0x1b3a}, {0x1b3c, 0x1b3c}, {0x1b42, 0x1b42},
|
||||||
|
{0x1b6b, 0x1b73}, {0x1b80, 0x1b81}, {0x1ba2, 0x1ba5},
|
||||||
|
{0x1ba8, 0x1ba9}, {0x1bab, 0x1bad}, {0x1be6, 0x1be6},
|
||||||
|
{0x1be8, 0x1be9}, {0x1bed, 0x1bed}, {0x1bef, 0x1bf1},
|
||||||
|
{0x1c2c, 0x1c33}, {0x1c36, 0x1c37}, {0x1cd0, 0x1cd2},
|
||||||
|
{0x1cd4, 0x1ce0}, {0x1ce2, 0x1ce8}, {0x1ced, 0x1ced},
|
||||||
|
{0x1cf4, 0x1cf4}, {0x1cf8, 0x1cf9}, {0x1dc0, 0x1df9},
|
||||||
|
{0x1dfb, 0x1dff}, {0x200b, 0x200f}, {0x202a, 0x202e},
|
||||||
|
{0x2060, 0x2064}, {0x2066, 0x206f}, {0x20d0, 0x20f0},
|
||||||
|
{0x2cef, 0x2cf1}, {0x2d7f, 0x2d7f}, {0x2de0, 0x2dff},
|
||||||
|
{0x302a, 0x302d}, {0x3099, 0x309a}, {0xa66f, 0xa672},
|
||||||
|
{0xa674, 0xa67d}, {0xa69e, 0xa69f}, {0xa6f0, 0xa6f1},
|
||||||
|
{0xa802, 0xa802}, {0xa806, 0xa806}, {0xa80b, 0xa80b},
|
||||||
|
{0xa825, 0xa826}, {0xa8c4, 0xa8c5}, {0xa8e0, 0xa8f1},
|
||||||
|
{0xa926, 0xa92d}, {0xa947, 0xa951}, {0xa980, 0xa982},
|
||||||
|
{0xa9b3, 0xa9b3}, {0xa9b6, 0xa9b9}, {0xa9bc, 0xa9bc},
|
||||||
|
{0xa9e5, 0xa9e5}, {0xaa29, 0xaa2e}, {0xaa31, 0xaa32},
|
||||||
|
{0xaa35, 0xaa36}, {0xaa43, 0xaa43}, {0xaa4c, 0xaa4c},
|
||||||
|
{0xaa7c, 0xaa7c}, {0xaab0, 0xaab0}, {0xaab2, 0xaab4},
|
||||||
|
{0xaab7, 0xaab8}, {0xaabe, 0xaabf}, {0xaac1, 0xaac1},
|
||||||
|
{0xaaec, 0xaaed}, {0xaaf6, 0xaaf6}, {0xabe5, 0xabe5},
|
||||||
|
{0xabe8, 0xabe8}, {0xabed, 0xabed}, {0xfb1e, 0xfb1e},
|
||||||
|
{0xfe00, 0xfe0f}, {0xfe20, 0xfe2f}, {0xfeff, 0xfeff},
|
||||||
|
{0xfff9, 0xfffb}, {0x101fd, 0x101fd}, {0x102e0, 0x102e0},
|
||||||
|
{0x10376, 0x1037a}, {0x10a01, 0x10a03}, {0x10a05, 0x10a06},
|
||||||
|
{0x10a0c, 0x10a0f}, {0x10a38, 0x10a3a}, {0x10a3f, 0x10a3f},
|
||||||
|
{0x10ae5, 0x10ae6}, {0x11001, 0x11001}, {0x11038, 0x11046},
|
||||||
|
{0x1107f, 0x11081}, {0x110b3, 0x110b6}, {0x110b9, 0x110ba},
|
||||||
|
{0x11100, 0x11102}, {0x11127, 0x1112b}, {0x1112d, 0x11134},
|
||||||
|
{0x11173, 0x11173}, {0x11180, 0x11181}, {0x111b6, 0x111be},
|
||||||
|
{0x111ca, 0x111cc}, {0x1122f, 0x11231}, {0x11234, 0x11234},
|
||||||
|
{0x11236, 0x11237}, {0x1123e, 0x1123e}, {0x112df, 0x112df},
|
||||||
|
{0x112e3, 0x112ea}, {0x11300, 0x11301}, {0x1133c, 0x1133c},
|
||||||
|
{0x11340, 0x11340}, {0x11366, 0x1136c}, {0x11370, 0x11374},
|
||||||
|
{0x11438, 0x1143f}, {0x11442, 0x11444}, {0x11446, 0x11446},
|
||||||
|
{0x114b3, 0x114b8}, {0x114ba, 0x114ba}, {0x114bf, 0x114c0},
|
||||||
|
{0x114c2, 0x114c3}, {0x115b2, 0x115b5}, {0x115bc, 0x115bd},
|
||||||
|
{0x115bf, 0x115c0}, {0x115dc, 0x115dd}, {0x11633, 0x1163a},
|
||||||
|
{0x1163d, 0x1163d}, {0x1163f, 0x11640}, {0x116ab, 0x116ab},
|
||||||
|
{0x116ad, 0x116ad}, {0x116b0, 0x116b5}, {0x116b7, 0x116b7},
|
||||||
|
{0x1171d, 0x1171f}, {0x11722, 0x11725}, {0x11727, 0x1172b},
|
||||||
|
{0x11a01, 0x11a06}, {0x11a09, 0x11a0a}, {0x11a33, 0x11a38},
|
||||||
|
{0x11a3b, 0x11a3e}, {0x11a47, 0x11a47}, {0x11a51, 0x11a56},
|
||||||
|
{0x11a59, 0x11a5b}, {0x11a8a, 0x11a96}, {0x11a98, 0x11a99},
|
||||||
|
{0x11c30, 0x11c36}, {0x11c38, 0x11c3d}, {0x11c3f, 0x11c3f},
|
||||||
|
{0x11c92, 0x11ca7}, {0x11caa, 0x11cb0}, {0x11cb2, 0x11cb3},
|
||||||
|
{0x11cb5, 0x11cb6}, {0x11d31, 0x11d36}, {0x11d3a, 0x11d3a},
|
||||||
|
{0x11d3c, 0x11d3d}, {0x11d3f, 0x11d45}, {0x11d47, 0x11d47},
|
||||||
|
{0x16af0, 0x16af4}, {0x16b30, 0x16b36}, {0x16f8f, 0x16f92},
|
||||||
|
{0x1bc9d, 0x1bc9e}, {0x1bca0, 0x1bca3}, {0x1d167, 0x1d169},
|
||||||
|
{0x1d173, 0x1d182}, {0x1d185, 0x1d18b}, {0x1d1aa, 0x1d1ad},
|
||||||
|
{0x1d242, 0x1d244}, {0x1da00, 0x1da36}, {0x1da3b, 0x1da6c},
|
||||||
|
{0x1da75, 0x1da75}, {0x1da84, 0x1da84}, {0x1da9b, 0x1da9f},
|
||||||
|
{0x1daa1, 0x1daaf}, {0x1e000, 0x1e006}, {0x1e008, 0x1e018},
|
||||||
|
{0x1e01b, 0x1e021}, {0x1e023, 0x1e024}, {0x1e026, 0x1e02a},
|
||||||
|
{0x1e8d0, 0x1e8d6}, {0x1e944, 0x1e94a}, {0xe0001, 0xe0001},
|
||||||
|
{0xe0020, 0xe007f}, {0xe0100, 0xe01ef},
|
||||||
|
};
|
||||||
|
|
||||||
|
/* test for 8-bit control characters */
|
||||||
|
if ( ucs == 0 ) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if ( ( ucs < 32 ) || ( ( ucs >= 0x7f ) && ( ucs < 0xa0 ) ) ) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* binary search in table of non-spacing characters */
|
||||||
|
if ( bisearch( ucs, combining, sizeof( combining ) / sizeof( struct interval ) - 1 ) ) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* if we arrive here, ucs is not a combining or C0/C1 control character */
|
||||||
|
return ( mk_is_wide_char( ucs ) ? 2 : 1 );
|
||||||
|
}
|
||||||
|
|
26
makefile
26
makefile
@ -9,18 +9,18 @@ HOST ?= linux
|
|||||||
CC ?= gcc
|
CC ?= gcc
|
||||||
WARNINGS_COMMON=-Wall -Wextra \
|
WARNINGS_COMMON=-Wall -Wextra \
|
||||||
-Wnull-dereference -Wshadow -Wformat=2 -Wswitch -Wswitch-enum \
|
-Wnull-dereference -Wshadow -Wformat=2 -Wswitch -Wswitch-enum \
|
||||||
-Wpedantic -Wstrict-prototypes -Wunused \
|
-Wpedantic -Wstrict-prototypes -Wunused -Wformat-nonliteral \
|
||||||
#-Wconversion -Wsign-conversion
|
#-Wconversion -Wsign-conversion
|
||||||
NOWARNINGS_COMMON=-Wno-address-of-packed-member
|
NOWARNINGS_COMMON=-Wno-address-of-packed-member
|
||||||
|
|
||||||
ifneq (,$(findstring gcc,$(CC)))
|
ifneq (,$(findstring gcc,$(CC)))
|
||||||
WARNINGS=$(WARNINGS_COMMON) -Wduplicated-cond -Wduplicated-branches -Wrestrict -Wlogical-op -Wjump-misses-init
|
WARNINGS=$(WARNINGS_COMMON) -Wduplicated-cond -Wduplicated-branches -Wrestrict -Wlogical-op -Wjump-misses-init
|
||||||
NOWARNINGS=$(NOWARNINGS_COMMON)
|
NOWARNINGS=$(NOWARNINGS_COMMON)
|
||||||
CFLAGS_BESTLINE=-Wno-logical-op
|
CFLAGS_ISOCLINE=-Wno-duplicated-branches
|
||||||
else ifneq (,$(findstring clang,$(CC)))
|
else ifneq (,$(findstring clang,$(CC)))
|
||||||
WARNINGS=$(WARNINGS_COMMON)
|
WARNINGS=$(WARNINGS_COMMON)
|
||||||
NOWARNINGS=$(NOWARNINGS_COMMON) -Wno-missing-prototype-for-cc
|
NOWARNINGS=$(NOWARNINGS_COMMON) -Wno-missing-prototype-for-cc
|
||||||
CFLAGS_BESTLINE=
|
CFLAGS_ISOCLINE=-Wno-format-nonliteral
|
||||||
else
|
else
|
||||||
$(error your compiler is not supported)
|
$(error your compiler is not supported)
|
||||||
endif
|
endif
|
||||||
@ -59,8 +59,8 @@ test: umka_shell
|
|||||||
umka_shell: umka_shell.o umka.o shell.o trace.o trace_lbr.o vdisk.o \
|
umka_shell: umka_shell.o umka.o shell.o trace.o trace_lbr.o vdisk.o \
|
||||||
vdisk/raw.o vdisk/qcow2.o deps/em_inflate/em_inflate.o vnet.o \
|
vdisk/raw.o vdisk/qcow2.o deps/em_inflate/em_inflate.o vnet.o \
|
||||||
$(HOST)/vnet/tap.o vnet/file.o lodepng.o $(HOST)/pci.o \
|
$(HOST)/vnet/tap.o vnet/file.o lodepng.o $(HOST)/pci.o \
|
||||||
$(HOST)/thread.o umkaio.o umkart.o deps/optparse/optparse32.o \
|
$(HOST)/thread.o umkaio.o umkart.o deps/optparse/optparse.o \
|
||||||
deps/bestline/bestline32.o
|
deps/isocline/src/isocline.o
|
||||||
$(CC) $(LDFLAGS_32) $^ -o $@ -T umka.ld
|
$(CC) $(LDFLAGS_32) $^ -o $@ -T umka.ld
|
||||||
|
|
||||||
umka_fuse: umka_fuse.o umka.o trace.o trace_lbr.o vdisk.o vdisk/raw.o \
|
umka_fuse: umka_fuse.o umka.o trace.o trace_lbr.o vdisk.o vdisk/raw.o \
|
||||||
@ -71,7 +71,7 @@ umka_fuse: umka_fuse.o umka.o trace.o trace_lbr.o vdisk.o vdisk/raw.o \
|
|||||||
umka_os: umka_os.o umka.o shell.o lodepng.o vdisk.o vdisk/raw.o vdisk/qcow2.o \
|
umka_os: umka_os.o umka.o shell.o lodepng.o vdisk.o vdisk/raw.o vdisk/qcow2.o \
|
||||||
deps/em_inflate/em_inflate.o vnet.o $(HOST)/vnet/tap.o vnet/file.o \
|
deps/em_inflate/em_inflate.o vnet.o $(HOST)/vnet/tap.o vnet/file.o \
|
||||||
trace.o trace_lbr.o $(HOST)/pci.o $(HOST)/thread.o umkaio.o umkart.o \
|
trace.o trace_lbr.o $(HOST)/pci.o $(HOST)/thread.o umkaio.o umkart.o \
|
||||||
deps/bestline/bestline32.o deps/optparse/optparse32.o
|
deps/isocline/src/isocline.o deps/optparse/optparse.o
|
||||||
$(CC) $(LDFLAGS_32) `sdl2-config --libs` $^ -o $@ -T umka.ld
|
$(CC) $(LDFLAGS_32) `sdl2-config --libs` $^ -o $@ -T umka.ld
|
||||||
|
|
||||||
umka_gen_devices_dat: umka_gen_devices_dat.o umka.o $(HOST)/pci.o \
|
umka_gen_devices_dat: umka_gen_devices_dat.o umka.o $(HOST)/pci.o \
|
||||||
@ -96,17 +96,13 @@ $(HOST)/pci.o: $(HOST)/pci.c
|
|||||||
lodepng.o: lodepng.c lodepng.h
|
lodepng.o: lodepng.c lodepng.h
|
||||||
$(CC) $(CFLAGS_32) -c $<
|
$(CC) $(CFLAGS_32) -c $<
|
||||||
|
|
||||||
deps/bestline/bestline32.o: deps/bestline/bestline.c deps/bestline/bestline.h
|
deps/isocline/src/isocline.o: deps/isocline/src/isocline.c \
|
||||||
$(CC) $(CFLAGS_32) $(CFLAGS_BESTLINE) -Wno-switch-enum -c $< -o $@
|
deps/isocline/include/isocline.h
|
||||||
|
$(CC) $(CFLAGS_32) $(CFLAGS_ISOCLINE) -c $< -o $@ -Wno-shadow \
|
||||||
deps/bestline/bestline.o: deps/bestline/bestline.c deps/bestline/bestline.h
|
-Wno-unused-function
|
||||||
$(CC) $(CFLAGS) -Wno-switch-enum -c $< -o $@
|
|
||||||
|
|
||||||
deps/optparse/optparse32.o: deps/optparse/optparse.c deps/optparse/optparse.h
|
|
||||||
$(CC) $(CFLAGS_32) -c $< -o $@
|
|
||||||
|
|
||||||
deps/optparse/optparse.o: deps/optparse/optparse.c deps/optparse/optparse.h
|
deps/optparse/optparse.o: deps/optparse/optparse.c deps/optparse/optparse.h
|
||||||
$(CC) $(CFLAGS) -c $< -o $@
|
$(CC) $(CFLAGS_32) -c $< -o $@
|
||||||
|
|
||||||
umkart.o: umkart.c umkart.h umka.h
|
umkart.o: umkart.c umkart.h umka.h
|
||||||
$(CC) $(CFLAGS_32) -c $<
|
$(CC) $(CFLAGS_32) -c $<
|
||||||
|
67
shell.c
67
shell.c
@ -39,7 +39,7 @@
|
|||||||
#include "umkart.h"
|
#include "umkart.h"
|
||||||
#include "lodepng.h"
|
#include "lodepng.h"
|
||||||
#include "optparse/optparse.h"
|
#include "optparse/optparse.h"
|
||||||
#include "bestline/bestline.h"
|
#include "isocline/include/isocline.h"
|
||||||
|
|
||||||
char *bestlineFile(const char *prompt, FILE *fin, FILE *fout);
|
char *bestlineFile(const char *prompt, FILE *fin, FILE *fout);
|
||||||
|
|
||||||
@ -380,21 +380,16 @@ prompt(struct shell_ctx *ctx) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
completion(const char *buf, bestlineCompletions *lc) {
|
completer(ic_completion_env_t *cenv, const char *prefix) {
|
||||||
if (buf[0] == 'h') {
|
(void)cenv;
|
||||||
bestlineAddCompletion(lc,"hello");
|
(void)prefix;
|
||||||
bestlineAddCompletion(lc,"hello there");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static char *
|
static void
|
||||||
hints(const char *buf, const char **ansi1, const char **ansi2) {
|
highlighter(ic_highlight_env_t *henv, const char *input, void *arg) {
|
||||||
if (!strcmp(buf,"hello")) {
|
(void)henv;
|
||||||
*ansi1 = "\033[35m"; /* magenta foreground */
|
(void)input;
|
||||||
*ansi2 = "\033[39m"; /* reset foreground */
|
(void)arg;
|
||||||
return " World";
|
|
||||||
}
|
|
||||||
return NULL;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static int
|
static int
|
||||||
@ -4010,18 +4005,37 @@ cmd_help(struct shell_ctx *ctx, int argc, char **argv) {
|
|||||||
|
|
||||||
void *
|
void *
|
||||||
run_test(struct shell_ctx *ctx) {
|
run_test(struct shell_ctx *ctx) {
|
||||||
|
int fdstdin = -1;
|
||||||
|
if (ctx->fin != stdin) {
|
||||||
|
fdstdin = dup(STDIN_FILENO);
|
||||||
|
close(STDIN_FILENO);
|
||||||
|
dup2(fileno(ctx->fin), STDIN_FILENO);
|
||||||
|
fclose(ctx->fin);
|
||||||
|
}
|
||||||
|
// ic_style_def("ic-prompt","ansi-default");
|
||||||
|
ic_enable_color(0);
|
||||||
|
ic_set_prompt_marker(NULL, NULL);
|
||||||
|
ic_enable_multiline(0);
|
||||||
|
ic_enable_beep(0);
|
||||||
|
|
||||||
pthread_mutex_lock(&ctx->cmd_mutex);
|
pthread_mutex_lock(&ctx->cmd_mutex);
|
||||||
int is_tty = isatty(fileno(ctx->fin));
|
int is_tty = isatty(fileno(ctx->fin));
|
||||||
char **argv = (char**)calloc(sizeof(char*), (MAX_COMMAND_ARGS + 1));
|
char **argv = (char**)calloc(sizeof(char*), (MAX_COMMAND_ARGS + 1));
|
||||||
bestlineSetCompletionCallback(completion);
|
ic_set_default_completer(&completer, NULL);
|
||||||
bestlineSetHintsCallback(hints);
|
ic_set_default_highlighter(highlighter, NULL);
|
||||||
bestlineHistoryLoad(ctx->hist_file);
|
ic_enable_auto_tab(1);
|
||||||
sprintf(prompt_line, "%s> ", last_dir);
|
sprintf(prompt_line, "%s", last_dir);
|
||||||
char *line;
|
char *line;
|
||||||
while((line = bestlineFile(prompt_line, ctx->fin, ctx->fout))) {
|
while((line = ic_readline(prompt_line)) || !feof(stdin)) {
|
||||||
if (!is_tty) {
|
if (!is_tty) {
|
||||||
prompt(ctx);
|
prompt(ctx);
|
||||||
fprintf(ctx->fout, "%s\n", line);
|
fprintf(ctx->fout, "%s\n", line ? line : "");
|
||||||
|
}
|
||||||
|
if (!line) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (line[0] == '\0') {
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
if (!strcmp(line, "X") || !strcmp(line, "q")) {
|
if (!strcmp(line, "X") || !strcmp(line, "q")) {
|
||||||
free(line);
|
free(line);
|
||||||
@ -4031,7 +4045,6 @@ run_test(struct shell_ctx *ctx) {
|
|||||||
free(line);
|
free(line);
|
||||||
continue;
|
continue;
|
||||||
} else {
|
} else {
|
||||||
bestlineHistoryAdd(line);
|
|
||||||
int argc = split_args(line, argv);
|
int argc = split_args(line, argv);
|
||||||
func_table_t *ft;
|
func_table_t *ft;
|
||||||
for (ft = cmd_cmds; ft->name; ft++) {
|
for (ft = cmd_cmds; ft->name; ft++) {
|
||||||
@ -4048,16 +4061,20 @@ run_test(struct shell_ctx *ctx) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
free(argv);
|
free(argv);
|
||||||
bestlineHistorySave(ctx->hist_file);
|
|
||||||
|
|
||||||
fclose(ctx->fin);
|
|
||||||
pthread_mutex_unlock(&ctx->cmd_mutex);
|
pthread_mutex_unlock(&ctx->cmd_mutex);
|
||||||
|
|
||||||
|
if (fdstdin != -1) {
|
||||||
|
close(STDIN_FILENO);
|
||||||
|
dup2(fdstdin, STDIN_FILENO);
|
||||||
|
close(fdstdin);
|
||||||
|
}
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct shell_ctx *
|
struct shell_ctx *
|
||||||
shell_init(int reproducible, const char *hist_file, struct umka_ctx *umka,
|
shell_init(int reproducible, const char *hist_file, struct umka_ctx *umka,
|
||||||
struct umka_io *io, FILE *fin, FILE *fout, const atomic_int *running) {
|
struct umka_io *io, FILE *fin, const atomic_int *running) {
|
||||||
struct shell_ctx *ctx = malloc(sizeof(struct shell_ctx));
|
struct shell_ctx *ctx = malloc(sizeof(struct shell_ctx));
|
||||||
ctx->umka = umka;
|
ctx->umka = umka;
|
||||||
ctx->io = io;
|
ctx->io = io;
|
||||||
@ -4065,7 +4082,7 @@ shell_init(int reproducible, const char *hist_file, struct umka_ctx *umka,
|
|||||||
ctx->hist_file = hist_file;
|
ctx->hist_file = hist_file;
|
||||||
ctx->var = NULL;
|
ctx->var = NULL;
|
||||||
ctx->fin = fin;
|
ctx->fin = fin;
|
||||||
ctx->fout = fout;
|
ctx->fout = stdout;
|
||||||
ctx->running = running;
|
ctx->running = running;
|
||||||
pthread_cond_init(&ctx->cmd_done, NULL);
|
pthread_cond_init(&ctx->cmd_done, NULL);
|
||||||
pthread_mutex_init(&ctx->cmd_mutex, NULL);
|
pthread_mutex_init(&ctx->cmd_mutex, NULL);
|
||||||
|
2
shell.h
2
shell.h
@ -51,7 +51,7 @@ struct shell_ctx {
|
|||||||
|
|
||||||
struct shell_ctx *
|
struct shell_ctx *
|
||||||
shell_init(int reproducible, const char *hist_file, struct umka_ctx *umka,
|
shell_init(int reproducible, const char *hist_file, struct umka_ctx *umka,
|
||||||
struct umka_io *io, FILE *fin, FILE *fout, const atomic_int *running);
|
struct umka_io *io, FILE *fin, const atomic_int *running);
|
||||||
|
|
||||||
void
|
void
|
||||||
shell_close(struct shell_ctx *shell);
|
shell_close(struct shell_ctx *shell);
|
||||||
|
@ -66,7 +66,7 @@ terminate_protection: 1
|
|||||||
keyboard_mode: 0
|
keyboard_mode: 0
|
||||||
captionEncoding: 0
|
captionEncoding: 0
|
||||||
exec_params: (null)
|
exec_params: (null)
|
||||||
wnd_caption:
|
wnd_caption: hi_there
|
||||||
wnd_clientbox (ltwh): 5 24 291 172
|
wnd_clientbox (ltwh): 5 24 291 172
|
||||||
priority: 0
|
priority: 0
|
||||||
in_schedule: prev (2), next (2)
|
in_schedule: prev (2), next (2)
|
||||||
|
@ -67,7 +67,7 @@ terminate_protection: 1
|
|||||||
keyboard_mode: 0
|
keyboard_mode: 0
|
||||||
captionEncoding: 0
|
captionEncoding: 0
|
||||||
exec_params: (null)
|
exec_params: (null)
|
||||||
wnd_caption:
|
wnd_caption: hi_there
|
||||||
wnd_clientbox (ltwh): 5 24 291 172
|
wnd_clientbox (ltwh): 5 24 291 172
|
||||||
priority: 0
|
priority: 0
|
||||||
in_schedule: prev (2), next (2)
|
in_schedule: prev (2), next (2)
|
||||||
|
@ -67,7 +67,7 @@ terminate_protection: 1
|
|||||||
keyboard_mode: 0
|
keyboard_mode: 0
|
||||||
captionEncoding: 0
|
captionEncoding: 0
|
||||||
exec_params: (null)
|
exec_params: (null)
|
||||||
wnd_caption:
|
wnd_caption: hi_there
|
||||||
wnd_clientbox (ltwh): 5 24 291 172
|
wnd_clientbox (ltwh): 5 24 291 172
|
||||||
priority: 0
|
priority: 0
|
||||||
in_schedule: prev (2), next (2)
|
in_schedule: prev (2), next (2)
|
||||||
|
35
umka_os.c
35
umka_os.c
@ -28,7 +28,7 @@
|
|||||||
#include "shell.h"
|
#include "shell.h"
|
||||||
#include "trace.h"
|
#include "trace.h"
|
||||||
#include "vnet.h"
|
#include "vnet.h"
|
||||||
#include "bestline/bestline.h"
|
#include "isocline/include/isocline.h"
|
||||||
#include "optparse/optparse.h"
|
#include "optparse/optparse.h"
|
||||||
|
|
||||||
#define HIST_FILE_BASENAME ".umka_os.history"
|
#define HIST_FILE_BASENAME ".umka_os.history"
|
||||||
@ -49,21 +49,16 @@ struct umka_os_ctx *os;
|
|||||||
char history_filename[PATH_MAX];
|
char history_filename[PATH_MAX];
|
||||||
|
|
||||||
static void
|
static void
|
||||||
completion(const char *buf, bestlineCompletions *lc) {
|
completer(ic_completion_env_t *cenv, const char *prefix) {
|
||||||
if (buf[0] == 'h') {
|
(void)cenv;
|
||||||
bestlineAddCompletion(lc,"hello");
|
(void)prefix;
|
||||||
bestlineAddCompletion(lc,"hello there");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static char *
|
static void
|
||||||
hints(const char *buf, const char **ansi1, const char **ansi2) {
|
highlighter(ic_highlight_env_t *henv, const char *input, void *arg) {
|
||||||
if (!strcmp(buf,"hello")) {
|
(void)henv;
|
||||||
*ansi1 = "\033[35m"; /* magenta foreground */
|
(void)input;
|
||||||
*ansi2 = "\033[39m"; /* reset foreground */
|
(void)arg;
|
||||||
return " World";
|
|
||||||
}
|
|
||||||
return NULL;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static int
|
static int
|
||||||
@ -74,13 +69,13 @@ hw_int_mouse(void *arg) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
struct umka_os_ctx *
|
struct umka_os_ctx *
|
||||||
umka_os_init(FILE *fin, FILE *fout, FILE *fboardlog) {
|
umka_os_init(FILE *fstartup, FILE *fboardlog) {
|
||||||
struct umka_os_ctx *ctx = malloc(sizeof(struct umka_os_ctx));
|
struct umka_os_ctx *ctx = malloc(sizeof(struct umka_os_ctx));
|
||||||
ctx->fboardlog = fboardlog;
|
ctx->fboardlog = fboardlog;
|
||||||
ctx->umka = umka_init();
|
ctx->umka = umka_init();
|
||||||
ctx->io = io_init(&ctx->umka->running);
|
ctx->io = io_init(&ctx->umka->running);
|
||||||
ctx->shell = shell_init(SHELL_LOG_NONREPRODUCIBLE, history_filename,
|
ctx->shell = shell_init(SHELL_LOG_NONREPRODUCIBLE, history_filename,
|
||||||
ctx->umka, ctx->io, fin, fout, &ctx->umka->running);
|
ctx->umka, ctx->io, fstartup, &ctx->umka->running);
|
||||||
return ctx;
|
return ctx;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -276,9 +271,9 @@ main(int argc, char *argv[]) {
|
|||||||
umka_sti();
|
umka_sti();
|
||||||
|
|
||||||
build_history_filename();
|
build_history_filename();
|
||||||
bestlineSetCompletionCallback(completion);
|
ic_set_default_completer(&completer, NULL);
|
||||||
bestlineSetHintsCallback(hints);
|
ic_set_default_highlighter(highlighter, NULL);
|
||||||
bestlineHistoryLoad(history_filename);
|
ic_enable_auto_tab(1);
|
||||||
|
|
||||||
const char *startupfile = NULL;
|
const char *startupfile = NULL;
|
||||||
const char *infile = NULL;
|
const char *infile = NULL;
|
||||||
@ -350,7 +345,7 @@ main(int argc, char *argv[]) {
|
|||||||
fboardlog = fout;
|
fboardlog = fout;
|
||||||
}
|
}
|
||||||
|
|
||||||
os = umka_os_init(fstartup, fout, fboardlog);
|
os = umka_os_init(fstartup, fboardlog);
|
||||||
|
|
||||||
struct sigaction sa;
|
struct sigaction sa;
|
||||||
sa.sa_sigaction = irq0;
|
sa.sa_sigaction = irq0;
|
||||||
|
@ -32,12 +32,12 @@ struct umka_shell_ctx {
|
|||||||
char history_filename[PATH_MAX];
|
char history_filename[PATH_MAX];
|
||||||
|
|
||||||
struct umka_shell_ctx *
|
struct umka_shell_ctx *
|
||||||
umka_shell_init(int reproducible, FILE *fin, FILE *fout) {
|
umka_shell_init(int reproducible, FILE *fin) {
|
||||||
struct umka_shell_ctx *ctx = malloc(sizeof(struct umka_shell_ctx));
|
struct umka_shell_ctx *ctx = malloc(sizeof(struct umka_shell_ctx));
|
||||||
ctx->umka = umka_init();
|
ctx->umka = umka_init();
|
||||||
ctx->io = io_init(NULL);
|
ctx->io = io_init(NULL);
|
||||||
ctx->shell = shell_init(reproducible, history_filename, ctx->umka, ctx->io,
|
ctx->shell = shell_init(reproducible, history_filename, ctx->umka, ctx->io,
|
||||||
fin, fout, &ctx->umka->running);
|
fin, &ctx->umka->running);
|
||||||
return ctx;
|
return ctx;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -118,14 +118,14 @@ main(int argc, char **argv) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (outfile) {
|
if (outfile) {
|
||||||
fout = fopen(outfile, "w");
|
fout = freopen(outfile, "w", stdout);
|
||||||
if (!fout) {
|
if (!fout) {
|
||||||
fprintf(stderr, "[!] can't open file for writing: %s\n", outfile);
|
fprintf(stderr, "[!] can't open file for writing: %s\n", outfile);
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct umka_shell_ctx *ctx = umka_shell_init(reproducible, fin, fout);
|
struct umka_shell_ctx *ctx = umka_shell_init(reproducible, fin);
|
||||||
|
|
||||||
if (coverage)
|
if (coverage)
|
||||||
trace_begin();
|
trace_begin();
|
||||||
|
@ -10,7 +10,7 @@
|
|||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
|
|
||||||
struct vnet *
|
struct vnet *
|
||||||
vnet_init_tap() {
|
vnet_init_tap(void) {
|
||||||
fprintf(stderr, "[vnet] tap interface isn't implemented for windows\n");
|
fprintf(stderr, "[vnet] tap interface isn't implemented for windows\n");
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user