C++程序  |  287行  |  6.18 KB

/* hexedit.c - Hexadecimal file editor
 *
 * Copyright 2015 Rob Landley <rob@landley.net>
 *
 * No standard

USE_HEXEDIT(NEWTOY(hexedit, "<1>1r", TOYFLAG_USR|TOYFLAG_BIN|TOYFLAG_LOCALE))

config HEXEDIT
  bool "hexedit"
  default y
  help
    usage: hexedit FILENAME

    Hexadecimal file editor.

    -r	Read only (display but don't edit)
*/

#define FOR_hexedit
#include "toys.h"

GLOBALS(
  char *data;
  long long len, base;
  int numlen;
  unsigned height;
)

static void esc(char *s)
{
  printf("\033[%s", s);
}

static void jump(int x, int y)
{
  char s[32];

  sprintf(s, "%d;%dH", y+1, x+1);
  esc(s);
}

static void fix_terminal(void)
{
  set_terminal(1, 0, 0);
  esc("?25h");
  esc("0m");
  jump(0, 999);
  esc("K");
}

static void sigttyreset(int i)
{
  fix_terminal();
  // how do I re-raise the signal so it dies with right signal info for wait()?
  _exit(127);
}

// Render all characters printable, using color to distinguish.
static void draw_char(char broiled)
{
  if (broiled<32 || broiled>=127) {
    if (broiled>127) {
      esc("2m");
      broiled &= 127;
    }
    if (broiled<32 || broiled==127) {
      esc("7m");
      if (broiled==127) broiled = 32;
      else broiled += 64;
    }
    printf("%c", broiled);
    esc("0m");
  } else printf("%c", broiled);
}

static void draw_tail(void)
{
  int i = 0, width = 0, w, len;
  char *start = *toys.optargs, *end;

  jump(0, TT.height);
  esc("K");

  // First time, make sure we fit in 71 chars (advancing start as necessary).
  // Second time, print from start to end, escaping nonprintable chars.
  for (i=0; i<2; i++) {
    for (end = start; *end;) {
      wchar_t wc;

      len = mbrtowc(&wc, end, 99, 0);
      if (len<0 || wc<32 || (w = wcwidth(wc))<0) {
        len = w = 1;
        if (i) draw_char(*end);
      } else if (i) fwrite(end, len, 1, stdout);
      end += len;

      if (!i) {
        width += w;
        while (width > 71) {
          len = mbrtowc(&wc, start, 99, 0);
          if (len<0 || wc<32 || (w = wcwidth(wc))<0) len = w = 1;
          width -= w;
          start += len;
        }
      }
    }
  }
}

static void draw_line(long long yy)
{
  int x, xx = 16;

  yy = (TT.base+yy)*16;
  if (yy+xx>=TT.len) xx = TT.len-yy;

  if (yy<TT.len) {
    printf("\r%0*llX ", TT.numlen, yy);
    for (x=0; x<xx; x++) printf(" %02X", TT.data[yy+x]);
    printf("%*s", 2+3*(16-xx), "");
    for (x=0; x<xx; x++) draw_char(TT.data[yy+x]);
    printf("%*s", 16-xx, "");
  }
  esc("K");
}

static void draw_page(void)
{
  int y;

  jump(0, 0);
  for (y = 0; y<TT.height; y++) {
    if (y) printf("\r\n");
    draw_line(y);
  }
  draw_tail();
}

// side: 0 = editing left, 1 = editing right, 2 = clear, 3 = read only
static void highlight(int xx, int yy, int side)
{
  char cc = TT.data[16*(TT.base+yy)+xx];
  int i;

  // Display cursor
  jump(2+TT.numlen+3*xx, yy);
  esc("0m");
  if (side!=2) esc("7m");
  if (side>1) printf("%02X", cc);
  else for (i=0; i<2;) {
    if (side==i) esc("32m");
    printf("%X", (cc>>(4*(1&++i)))&15);
  }
  esc("0m");
  jump(TT.numlen+17*3+xx, yy);
  draw_char(cc);
}

#define KEY_UP 256
#define KEY_DOWN 257
#define KEY_RIGHT 258
#define KEY_LEFT 259
#define KEY_PGUP 260
#define KEY_PGDN 261
#define KEY_HOME 262
#define KEY_END  263
#define KEY_INSERT 264

void hexedit_main(void)
{
  // up down right left pgup pgdn home end ins
  char *keys[] = {"\033[A", "\033[B", "\033[C", "\033[D", "\033[5~", "\033[6~",
                  "\033OH", "\033OF", "\033[2~", 0};
  long long pos;
  int x, y, i, side = 0, key, ro = toys.optflags&FLAG_r,
      fd = xopen(*toys.optargs, ro ? O_RDONLY : O_RDWR);

  TT.height = 25;
  terminal_size(0, &TT.height);
  if (TT.height) TT.height--;
  sigatexit(sigttyreset);
  esc("0m");
  esc("?25l");
  fflush(0);
  set_terminal(1, 1, 0);

  if ((TT.len = fdlength(fd))<0) error_exit("bad length");
  if (sizeof(long)==32 && TT.len>SIZE_MAX) TT.len = SIZE_MAX;
  // count file length hex digits, rounded up to multiple of 4
  for (pos = TT.len, TT.numlen = 0; pos; pos >>= 4, TT.numlen++);
  TT.numlen += (4-TT.numlen)&3;

  TT.data = mmap(0, TT.len, PROT_READ|(PROT_WRITE*!ro), MAP_SHARED, fd, 0);

  draw_page();

  y = x = 0;
  for (;;) {
    // Get position within file, trimming if we overshot end.
    pos = 16*(TT.base+y)+x;
    if (pos>=TT.len) {
      pos = TT.len-1;
      x = pos&15;
      y = (pos/16)-TT.base;
    }

    // Display cursor
    highlight(x, y, ro ? 3 : side);
    xprintf("");

    // Wait for next key
    key = scan_key(toybuf, keys, 1);
    // Exit for q, ctrl-c, ctrl-d, escape, or EOF
    if (key==-1 || key==3 || key==4 || key==27 || key=='q') break;
    highlight(x, y, 2);

    if (key>='a' && key<='f') key-=32;
    if (!ro && ((key>='0' && key<='9') || (key>='A' && key<='F'))) {
      i = key - '0';
      if (i>9) i -= 7;
      TT.data[pos] &= 15<<(4*side);
      TT.data[pos] |= i<<(4*!side);

      highlight(x, y, ++side);
      if (side==2) {
        side = 0;
        if (++pos<TT.len && ++x==16) {
          x = 0;
          if (++y == TT.height) {
            --y;
            goto down;
          }
        }
      }
    }
    if (key>255) side = 0;
    if (key==KEY_UP) {
      if (--y<0) {
        if (TT.base) {
          TT.base--;
          esc("1T");
          draw_tail();
          jump(0, 0);
          draw_line(0);
        }
        y = 0;
      }
    } else if (key==KEY_DOWN) {
      if (y == TT.height-1 && (pos|15)+1<TT.len) {
down:
        TT.base++;
        esc("1S");
        jump(0, TT.height-1);
        draw_line(TT.height-1);
        draw_tail();
      }
      if (++y>=TT.height) y--;
    } else if (key==KEY_RIGHT) {
      if (x<15 && pos+1<TT.len) x++;
    } else if (key==KEY_LEFT) {
      if (x) x--;
    } else if (key==KEY_PGUP) {
      TT.base -= TT.height;
      if (TT.base<0) TT.base = 0;
      draw_page();
    } else if (key==KEY_PGDN) {
      TT.base += TT.height;
      if ((TT.base*16)>=TT.len) TT.base=(TT.len-1)/16;
      while ((TT.base+y)*16>=TT.len) y--;
      if (16*(TT.base+y)+x>=TT.len) x = (TT.len-1)&15;
      draw_page();
    } else if (key==KEY_HOME) {
      TT.base = 0;
      x = 0;
      draw_page();
    } else if (key==KEY_END) {
      TT.base=(TT.len-1)/16;
      x = (TT.len-1)&15;
      draw_page();
    }
  }
  munmap(TT.data, TT.len);
  close(fd);
  fix_terminal();
}