Goto sanos source index

//
// readline.c
//
// Read line with editing
//
// Copyright (C) 2002 Michael Ringgaard. All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions
// are met:
// 
// 1. Redistributions of source code must retain the above copyright 
//    notice, this list of conditions and the following disclaimer.  
// 2. 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.  
// 3. Neither the name of the project nor the names of its contributors
//    may be used to endorse or promote products derived from this software
//    without specific prior written permission. 
// 
// 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 OWNER 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.
// 

#include <os.h>
#include <stdio.h>
#include <string.h>

#define MAX_HISTORY     64

#define KEY_BACKSPACE   0x101
#define KEY_ESC         0x102
#define KEY_INS         0x103
#define KEY_DEL         0x104
#define KEY_LEFT        0x105
#define KEY_RIGHT       0x106
#define KEY_UP          0x107
#define KEY_DOWN        0x108
#define KEY_CTRL_LEFT   0x109
#define KEY_CTRL_RIGHT  0x10A
#define KEY_HOME        0x10B
#define KEY_END         0x10C
#define KEY_ENTER       0x10D
#define KEY_TAB         0x10E
#define KEY_EOF         0x10F

#define KEY_UNKNOWN     0xFFF

static int insmode = 1;
static int history_len = 0;
static char *history[MAX_HISTORY];
int _break_on_escape = 0;

void add_to_history(char *line) {
  int i;

  if (!line || !*line) return;
  if (history_len > 0 && strcmp(line, history[history_len - 1]) == 0) return;

  if (history_len == MAX_HISTORY) {
    free(history[0]);
    for (i = 0; i < history_len - 1; i++) history[i] = history[i + 1];
    history_len--;
  }

  history[history_len] = malloc(strlen(line) + 1);
  if (history[history_len]) {
    strcpy(history[history_len], line);
    history_len++;
  }
}

static int like(char *str, char *mask) {
  if (!str) str = "";
  if (!mask) mask = "";

  while (*mask) {
    if (*mask == '*') {
      while (*mask == '*') mask++;
      if (*mask == 0) return 1;
      while (*str) {
        if (like(str, mask)) return 1;
        str++;
      }
      return 0;
    } else if (*mask == *str || *mask == '?' && *str != 0) {
      str++;
      mask++;
    } else {
      return 0;
    }
  }

  return *str == 0;
}

static int delimchar(int ch) {
  if (ch == ' ' || ch == ',' || ch == ';') return 1;
  return 0;
}

static int find_dir(char *buf, int start, int end, int split, char *mask) {
  char path[MAXPATH];
  int wildcards;
  int idx;
  int dir;
  
  if (start == split) {
    path[0] = '.';
    path[1] = 0;
  } else {
    memcpy(path, buf + start, split - start);
    path[split - start] = 0;
  }

  dir = _opendir(path);
  if (dir < 0) return dir;

  if (split == end) {
    *mask++ = '*';
  } else {
    wildcards = 0;
    idx = split;
    while (idx < end) {
      if (buf[idx] == '*' || buf[idx] == '?') wildcards = 1;
      *mask++ = buf[idx++];
    }

    if (!wildcards) *mask++ = '*';
  }

  *mask++ = 0;
  return dir;
}

static int getkey() {
  int ch;

  ch = getchar();
  if (ch < 0) return ch;

  switch (ch) {
    case 0x08: return KEY_BACKSPACE;
    case 0x09: return KEY_TAB;
    case 0x0D: return gettib()->proc->term->type == TERM_CONSOLE ? KEY_ENTER : KEY_UNKNOWN;
    case 0x0A: return gettib()->proc->term->type != TERM_CONSOLE ? KEY_ENTER : KEY_UNKNOWN;
    case 0x04: return KEY_EOF;

    case 0x1B:
      ch = getchar();
      switch (ch) {
        case 0x1B: return KEY_ESC;
        case 0x5B:
          ch = getchar();
          switch (ch) {
            case 0x41: return KEY_UP;
            case 0x42: return KEY_DOWN;
            case 0x43: return KEY_RIGHT;
            case 0x44: return KEY_LEFT;

            default: return KEY_UNKNOWN;
          }
          break;

        default: return KEY_UNKNOWN;
      }
      break;

    case 0x00:
    case 0xE0:
      ch = getchar();
      switch (ch) {
        case 0x47: return KEY_HOME;
        case 0x48: return KEY_UP;
        case 0x4B: return KEY_LEFT;
        case 0x4D: return KEY_RIGHT;
        case 0x4F: return KEY_END;
        case 0x50: return KEY_DOWN;
        case 0x52: return KEY_INS;
        case 0x53: return KEY_DEL;
        case 0x73: return KEY_CTRL_LEFT;
        case 0x74: return KEY_CTRL_RIGHT;
        default: return KEY_UNKNOWN;
      }
      break;

    case 0x7F: return KEY_BACKSPACE;

    default: return ch;
  }
}

int readline(char *buf, int size) {
  int idx;
  int len;
  int key;
  int i;
  int done;
  int hist_idx;
  int dir;

  if (size <= 0) {
    errno = EINVAL;
    return -1;
  }

  idx = 0;
  len = 0;
  done = 0;
  hist_idx = history_len;
  while (!done) {
    fflush(stdout);
    key = getkey();
    if (key < 0) return key;

    if (key == KEY_TAB) {
      int start;
      int end;
      int split;
      char mask[MAXPATH];
      struct direntry dirent;

      start = idx;
      while (start > 0 && !delimchar(buf[start - 1])) start--;
      end = split = start;
      while (end < len && !delimchar(buf[end])) {
        if (buf[end] == PS1 || buf[end] == PS2) split = end + 1;
        end++;
      }

      dir = find_dir(buf, start, end, split, mask);
      if (dir >= 0) {
        while (_readdir(dir, &dirent, 1) > 0) {
          int newlen = len - (end - split) + dirent.namelen;
          
          if (like(dirent.name, mask) && newlen < size - 1) {
            memmove(buf + split + dirent.namelen, buf + end, len - end);
            memcpy(buf + split, dirent.name, dirent.namelen);

            while (idx < split) putchar(buf[idx++]);
            while (idx > split)  {
              putchar('\b');
              idx--;
            }

            for (i = split; i < newlen; i++) putchar(buf[i]);
            if (newlen < len) {
              for (i = newlen; i < len; i++) putchar(' ');
              for (i = newlen; i < len; i++) putchar('\b');
            }

            end = split + dirent.namelen;
            len = newlen;
            idx = end;

            for (i = end; i < len; i++) putchar('\b');

            fflush(stdout);
            key = getkey();
            if (key < 0) break;
            if (key != KEY_TAB) break;
          }
        }
        close(dir);
        if (key < 0) return key;
      }
    }

    switch (key) {
      case KEY_LEFT:
        if (idx > 0) {
          putchar('\b');
          idx--;
        }
        break;

      case KEY_RIGHT:
        if (idx < len) {
          putchar(buf[idx]);
          idx++;
        }
        break;

      case KEY_CTRL_LEFT:
        if (idx > 0) {
          putchar('\b');
          idx--;
        }
        while (idx > 0 && buf[idx - 1] != ' ') {
          putchar('\b');
          idx--;
        }
        break;

      case KEY_CTRL_RIGHT:
        while (idx < len && buf[idx] != ' ') {
          putchar(buf[idx]);
          idx++;
        }
        if (idx < len) {
          putchar(buf[idx]);
          idx++;
        }
        break;

      case KEY_HOME:
        while (idx > 0) {
          putchar('\b');
          idx--;
        }
        break;

      case KEY_END:
        while (idx < len) {
          putchar(buf[idx]);
          idx++;
        }
        break;

      case KEY_DEL:
        if (idx < len) {
          len--;
          memmove(buf + idx, buf + idx + 1, len - idx);
          for (i = idx; i < len; i++) putchar(buf[i]);
          putchar(' ');
          putchar('\b');
          for (i = idx; i < len; i++) putchar('\b');
        }
        break;

      case KEY_INS:
        insmode = !insmode;
        break;

      case KEY_BACKSPACE:
        if (idx > 0) {
          putchar('\b');
          idx--;
          len--;
          memmove(buf + idx, buf + idx + 1, len - idx);
          for (i = idx; i < len; i++) putchar(buf[i]);
          putchar(' ');
          putchar('\b');
          for (i = idx; i < len; i++) putchar('\b');
        }
        break;

      case KEY_ESC:
        if (_break_on_escape) {
          buf[len] = 0;
          errno = EINTR;
          return -1;
        } else {
          for (i = 0; i < idx; i++) putchar('\b');
          for (i = 0; i < len; i++) putchar(' ');
          for (i = 0; i < len; i++) putchar('\b');
          idx = len = 0;
        }
        break;

      case KEY_EOF:
        if (len == 0) return -1;
        break;

      case KEY_ENTER:
        putchar('\r');
        putchar('\n');
        done = 1;
        break;

      case KEY_UP:
        if (hist_idx > 0) {
          hist_idx--;
          for (i = 0; i < idx; i++) putchar('\b');
          for (i = 0; i < len; i++) putchar(' ');
          for (i = 0; i < len; i++) putchar('\b');
          len = strlen(history[hist_idx]);
          if (len > size - 1) len = size - 1;
          idx = len;
          memcpy(buf, history[hist_idx], len);
          for (i = 0; i < len; i++) putchar(buf[i]);
        }
        break;

      case KEY_DOWN:
        if (hist_idx < history_len - 1) {
          hist_idx++;
          for (i = 0; i < idx; i++) putchar('\b');
          for (i = 0; i < len; i++) putchar(' ');
          for (i = 0; i < len; i++) putchar('\b');
          len = strlen(history[hist_idx]);
          if (len > size - 1) len = size - 1;
          idx = len;
          memcpy(buf, history[hist_idx], len);
          for (i = 0; i < len; i++) putchar(buf[i]);
        }
        break;

      case KEY_UNKNOWN:
        break;

      default:
        if (key >= 0x20 && key <= 0xFF) {
          if (insmode) {
            if (len < size - 1) {
              if (idx < len) memmove(buf + idx + 1, buf + idx, len - idx);
              buf[idx] = key;
              len++;
              for (i = idx; i < len; i++) putchar(buf[i]);
              idx++;
              for (i = idx; i < len; i++) putchar('\b');
            }
          } else {
            if (idx < size - 1) {
              buf[idx] = key;
              putchar(buf[idx]);
              if (idx == len) len++;
              idx++;
            }
          }
        }
    }
  }

  buf[len] = 0;

  add_to_history(buf);

  return len;
}