/* File: cdargs.cc * * This file is part of cdargs * * Copyright (C) 2001-2003 by Stefan Kamphausen * Author: Stefan Kamphausen * * Time-stamp: <26-Feb-2006 18:06:47 ska> * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ /********************************************************************/ /* MAN, IF YOU DIDN'T TRY TO FIX THIS CODE YOU DON'T KNWO HOW MUCH */ /* IT NEEDS A COMPLETE REWRITE */ /* (the author) */ /********************************************************************/ // damn, if you remove this, you get lots of trouble that _I_ don't // wanna get involved in. I've been reading include files for more // than one hour now, and that's my decision. The code is a mess // anyway. #ifndef _GNU_SOURCE # define _GNU_SOURCE #endif /* C++ Includes */ # include # include # include # include # include # include using namespace std; /* C Includes */ # include # include # include # include # include # include # include # include # include //# if defined(USE_NCURSES) && !defined(RENAMED_NCURSES) # if defined(HAVE_NCURSES_H) # include # else # include # endif # include "cdargs.h" /* GLOBALS */ // FIXME: this file has too many globals. Especially the // offset/position handling is UGLY! // // Change this if you want different length (actually width) for the // description: # define DESCRIPTION_MAXLENGTH 18 # define DESCRIPTION_BROWSELENGTH 8 const char* DefaultListfile = ".cdargs"; const char* DefaultResultfile = "~/.cdargsresult"; const char* Needle = NULL; bool NeedleGiven = false; int CurrPosition = 0; map LastPositions; // terminal coordinates and other curses stuff int terminal_width, terminal_height; int xmax; int display_area_ymax; int display_area_ymin; int modeliney; int msg_area_y; int yoffset = 0; bool curses_running = false; int shorties_offset = 0; // cdargs modi int mode = LIST; bool show_hidden_files = false; bool opt_no_wrap = false; bool opt_no_resolve = false; bool listfile_empty = false; bool opt_cwd = false; string opt_resultfile=""; string opt_listfile=""; string opt_user=""; int main(int argc, char** argv) { int c; /* parse command line args */ while (1) { int option_index = 0; static struct option long_options[] = { {"add" , 1, 0, 0}, {"file" , 1, 0, 0}, {"user" , 1, 0, 0}, {"browse" , 0, 0, 0}, {"nowrap" , 0, 0, 0}, {"noresolve" , 0, 0, 0}, {"cwd" , 0, 0, 0}, {"output" , 1, 0, 0}, {"version" , 0, 0, 0}, {"help" , 0, 0, 0}, {0, 0, 0, 0} }; c = getopt_long (argc, argv, "a:f:u:brco:vh", long_options, &option_index); if (c == -1) { break; } string optname; string argument; switch (c) { case 0: optname = string(long_options[option_index].name); if (optname == "help") { version(); usage(); exit(0); } if (optname == "version") { version(); exit(0); } if (optname == "add") { argument = string(optarg); add_to_list_file(argument); exit(0); } if (optname == "file") { opt_listfile = string(optarg); } if (optname == "user") { opt_user = string(optarg); } if(optname == "browse") { mode = BROWSE; } if (optname == "nowrap") { opt_no_wrap = true; } if (optname == "noresolve") { opt_no_resolve = true; } if (optname == "cwd") { opt_cwd = true; } if (optname == "output") { opt_resultfile = string(optarg); } break; case 'a': argument = string(optarg); add_to_list_file(argument); exit(0); break; case 'f': opt_listfile = string(optarg); break; case 'u': opt_user = string(optarg); break; case 'b': mode = BROWSE; break; case 'r': opt_no_resolve = true; break; case 'c': opt_cwd = true; break; case 'o': opt_resultfile = string(optarg); break; case 'h': version(); usage(); exit(0); break; case 'v': version(); exit(0); default: usage(); exit(1); } } if (optind < argc) { Needle = argv[optind]; if(strlen(Needle) > 0) { NeedleGiven = true; } if(strlen(Needle) == 1 && isdigit(Needle[0])) { CurrPosition = atoi(Needle); Needle = NULL; } } // leave terminal tidy // FIXME: what other signals do I need to catch? signal(SIGINT, terminate); signal(SIGTERM, terminate); signal(SIGSEGV, terminate); init_curses(); // answer to terminal reizing signal(SIGWINCH, resizeevent); /* get list from file or start in browse mode */ if (!list_from_file()) { /* doesn't exist. browse current dir */ mode = BROWSE; } // if we're browsing read the CWD if(mode == BROWSE) { list_from_dir(); } /* main event loop */ /* determines the entry to use */ while (1) { display_list(); c = getch(); if (!user_interaction(c)) { break; } } finish(current_entry(), true); exit(1); } void toggle_mode(void) { if (mode == LIST) { mode = BROWSE; list_from_dir("."); } else { if(listfile_empty) { // list was empty at start if(!default_list.empty()) { // but isn't now list_to_file(); list_from_file(); mode = LIST; } else { // ok, we have nothing to show here message("No List entry. Staying in BROWSE mode"); mode = BROWSE; list_from_dir("."); } } else { // the "normal" case; // disable Needle and reload the list! NeedleGiven = false; Needle = NULL; list_from_file(); yoffset = 0; shorties_offset = 0; CurrPosition = 0; mode = LIST; } } } void toggle_hidden(void) { show_hidden_files = !show_hidden_files; if (mode == BROWSE) { list_from_dir(); } } bool user_interaction(int c) { int num; string curen; switch(c) { // ==== Exits case CTRL('['): // vi case CTRL('g'): // emacs abort_cdargs(); break; case 'q': finish(".", false); break; case 13: // ENTER // choose dir at cur pos return false; break; case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': num = c-'0'+shorties_offset; if(mode == LIST) { if (entry_nr_exists(num)) { CurrPosition = num+yoffset; return opt_no_resolve?true:false; } } else { CurrPosition = num+yoffset; return true; } break; // ==== Modes case '.': // show hidden files toggle_hidden(); break; case '\t': // TAB toggle_mode(); break; // ==== Navigate The List case 'j': // vi case CTRL('n'): // emacs #if TERMINFO case KEY_DOWN: #endif // navigate list++ cur_pos_adjust(+1); break; case 'k': // vi case CTRL('p'): // emacs #if TERMINFO case KEY_UP: #endif // navigate list-- cur_pos_adjust(-1); break; case '^': // vi? case CTRL('a'): // emacs #if TERMINFO case KEY_HOME: #endif // go to top CurrPosition = 0; yoffset = 0; break; case '$': // vi case CTRL('e'): // emacs # if TERMINFO case KEY_END: #endif // go to end if (mode == BROWSE) { CurrPosition = cur_list.size()-1; } else { CurrPosition = default_list.size()-1; } yoffset = max_yoffset(); break; //==== move the shortcut digits ('shorties') // FIXME case ??: //vi // FIXME maybe change the scrolling behaviour to not take the window but the whole // list? That means recentering when shorties leave the screen (adjust yoffset and // CurrPosition) case CTRL('v'): // emacs #if TERMINFO case KEY_NPAGE: #endif for(int i=0;i<10;i++) { cur_pos_adjust(+1,false); } break; // fixme: vi? //case CTRL(''): // FIXME: META(x)?? #if TERMINFO case KEY_PPAGE: #endif for(int i=0;i<10;i++) { cur_pos_adjust(-1,false); } break; case 'h': // vi case CTRL('b'): // emacs #if TERMINFO case KEY_LEFT: #endif // up dir if (mode == BROWSE) { // LastPositions[get_current_dir_name()] = CurrPosition; LastPositions[get_cwd_as_charp()] = CurrPosition; } else { mode = BROWSE; } list_from_dir(".."); break; case 'l': // vi case CTRL('f'): // emacs #if TERMINFO case KEY_RIGHT: #endif // descend dir at cur pos curen = current_entry(); if (mode == BROWSE) { LastPositions[get_cwd_as_charp()] = CurrPosition; } else { mode = BROWSE; } list_from_dir(curen.c_str()); break; case 'H': case '?': helpscreen(); break; case 'd': case CTRL('d'): // emacs #if TERMINFO case KEY_BACKSPACE: #endif // delete dir acp if (mode == LIST && !NeedleGiven) { delete_from_default_list(CurrPosition); } else { beep(); } break; #if TERMINFO case KEY_IC: #endif case 'a': // add dir acp (if in browse mode) if(!NeedleGiven) { if (mode == BROWSE) { add_to_default_list(current_entry()); } else { add_to_default_list(get_cwd_as_string()); } } else { beep(); } break; case 'A': if(!NeedleGiven) { // add dir acp (if in browse mode) (ask for desc) if (mode == BROWSE) { add_to_default_list(current_entry(),"",true); } else { add_to_default_list(get_cwd_as_string(),"",true); } } else { beep(); } break; case 'c': if(!NeedleGiven) { // add the current dir in every mode add_to_default_list(get_cwd_as_string()); } else { beep(); } break; case 'C': if(!NeedleGiven) { // add the current dir in every mode (ask for desc) add_to_default_list(get_cwd_as_string(),"",true); } else { beep(); } break; // ==== EDIT the list case 'v': case 'e': { if(!NeedleGiven) { edit_list_file(); } else { beep(); } break; } case 'm': swap_two_entries(1); break; case 'M': swap_two_entries(-1); break; case 't': case 's': swap_two_entries(0); break; // ==== Filesystem Hotspots case '~': mode = BROWSE; list_from_dir(getenv("HOME")); break; case '/': mode = BROWSE; list_from_dir("/"); break; default: beep(); message("unknown command"); } return true; } void swap_two_entries(int advance_afterwards) { int switch_index; if(advance_afterwards >= 0) { switch_index = CurrPosition + 1; } else { switch_index = CurrPosition -1; } // ranges check: if(switch_index >= int(default_list.size()) || switch_index < 0) { beep(); return; } if (mode == LIST && !NeedleGiven) { pair tmp = default_list[CurrPosition]; default_list[CurrPosition] = default_list[switch_index]; default_list[switch_index] = tmp; cur_pos_adjust(advance_afterwards); display_list(); } else { beep(); } } string get_description_from_user(void) { char desc[DESCRIPTION_MAXLENGTH+1]; move(msg_area_y, 0); clrtoeol(); printw("Enter description ( to quit): "); refresh(); echo(); getnstr(desc, DESCRIPTION_MAXLENGTH); noecho(); move(msg_area_y, 0); clrtoeol(); return string(desc); } bool list_from_file(void) { string desc; string path; int linecount=0; default_list.clear(); string mylistfile = get_listfile(); ifstream listfile(mylistfile.c_str()); if (!listfile) { return false; } string cwd = get_cwd_as_string(); while (! listfile.eof()) { string line; getline(listfile, line); if(line == "") continue; // comments ... are not saved later so we simply don't allow // them // if (line[0] == '#') { // continue; // } // detect path and description at the leading slash of the // absolute path: desc = line.substr(0,line.find('/')-1); path = line.substr(line.find('/')); if(opt_cwd && cwd == path) { CurrPosition = linecount; } // counting the lines: if only one, no resolving should take place linecount++; // if we got an exact match and not opt_no_resolve the first entry is // the result! if(NeedleGiven && Needle && Needle == desc && !opt_no_resolve) { finish(path,true); } // filtered? if (do_not_show(desc.c_str()) && do_not_show(path.c_str())) { continue; } default_list.push_back(make_pair(desc, path)); } listfile.close(); // some magic: switch(linecount) { case 0: listfile_empty = true; break; case 1: opt_no_resolve=true; break; } shorties_offset = 0; if(default_list.empty()) { return false; } return true; } // default: name="." void list_from_dir(const char* name) { string previous_dir = ""; if(name == "..") { previous_dir = get_cwd_as_string(); } // Checking, Changing and Reading if (chdir(name) < 0) { string msg = "couldn't change to dir: "; msg += name; message(msg.c_str()); } DIR* THISDIR; string cwd = get_cwd_as_string(); THISDIR = opendir(cwd.c_str()); if (THISDIR == NULL) { string msg = "couldn't read dir: "; msg += cwd; message(msg.c_str()); sleep(1); abort_cdargs(); } yoffset = 0; shorties_offset = 1; struct dirent* en; struct stat buf; // cleanup cur_list.clear(); CurrPosition = 1; // put the current directory on top ef every listing string desc = "."; cur_list.push_back(make_pair(desc,cwd)); // read home dir while ((en = readdir(THISDIR)) != NULL) { /* filter dirs */ if (do_not_show(en->d_name)) continue; string fullname; fullname = cwd + string("/") + string(en->d_name); fullname=canonify_filename(fullname); stat(fullname.c_str(), &buf); if (!S_ISDIR(buf.st_mode)) continue; string lastdir = last_dirname(fullname); cur_list.push_back(make_pair(lastdir, fullname)); } // empty directory listing: .. and . desc=".."; string path=get_cwd_as_string(); if(cur_list.size() == 1) { path = path.substr(0,path.find(last_dirname(path))); cur_list.push_back(make_pair(desc,path)); } sort(cur_list.begin(),cur_list.end()); // have we remembered a current position for this dir? if (LastPositions.count(cwd.c_str())) { CurrPosition = LastPositions[cwd.c_str()]; } else { //CurrPosition = 1; if(previous_dir.length() > 0) { int count = 0; for(listit it=cur_list.begin(); it!=cur_list.end();++it, count++) { if(it->second == previous_dir) { CurrPosition = count; break; } } } } if(!visible(CurrPosition)) { yoffset = CurrPosition - 1; } closedir(THISDIR); } void list_to_file(void) { // don't write empty files FIXME: unlink file? string mylistfile = get_listfile(); if(default_list.empty()) { unlink(mylistfile.c_str()); return; } // never touch the listfile of another user if(opt_user.size() > 0) { return; } string backup_file = mylistfile + "~"; if(rename(mylistfile.c_str(),backup_file.c_str()) == -1) { fprintf(stderr,"warning: could not create backupfile\n"); } ofstream listfile(mylistfile.c_str()); if (!listfile) { fatal_exit("couldn't write listfile"); } for (listit li = default_list.begin();li != default_list.end(); ++li) { listfile << li->first << " " << li->second << endl; } listfile.close(); } bool do_not_show(const char* name) { /* don't show current, up, hidden if not flag */ if (mode == BROWSE) { if (strcmp(name, "..") == 0) return true; if (!show_hidden_files && name[0] == '.') return true; } else { if (!NeedleGiven) { return false; } if(!Needle) return false; /* FIXME case-insensitive comparison */ //if (strcasecmp(name, Needle)) { if (strstr(name, Needle)) { return false; } else { return true; } } return false; } // default: n=0, // wraparound=true void cur_pos_adjust(int n, bool wraparound) { int newpos = CurrPosition + n; int max, min; if (mode == LIST) { max = default_list.size() - 1; } else { max = cur_list.size() - 1; } min = 0; if (newpos < min) { if (opt_no_wrap || !wraparound) return; newpos = max; yoffset = max_yoffset(); } if (newpos > max) { if (opt_no_wrap || !wraparound) return; newpos = min; yoffset = 0; } CurrPosition = newpos; // scrolling... if (CurrPosition-yoffset >= display_area_ymax) { yoffset++; } if (yoffset > 0 && CurrPosition == yoffset) { yoffset--; } } bool entry_nr_exists(unsigned int nr) { if (mode == LIST) { return nr(DESCRIPTION_MAXLENGTH+1)) { fprintf(stderr,"description too long! max %d chars\n", DESCRIPTION_MAXLENGTH); exit(-4); } else { desc = path.substr(1,colon2_at-1); } path = path.substr(colon2_at+1); } // FIXME: check for existance here? if(path.at(0) != '/') { fprintf(stderr,"this is not an absolute path:\n%s\n",path.c_str()); exit(-2); } list_from_file(); add_to_default_list(path,desc,false); list_to_file(); } void delete_from_default_list(int pos) { int count = 0; for (listit li = default_list.begin();li != default_list.end(); ++li) { if (count == pos) { default_list.erase(li); cur_pos_adjust(0,false); // we want no one-entry-in-list-magic anymore opt_no_resolve=true; return; } else { count++; } } } void edit_list_file(void) { char* editor = getenv("EDITOR"); if (!editor) { // FIXME: how to detect debian and the /usr/bin/editor rule? struct stat buf; if(stat("/usr/bin/editor",&buf) == 0) editor = "/usr/bin/editor"; else editor = "vi"; } endwin(); list_to_file(); system((string(editor) + " \"" + get_listfile() + "\"").c_str()); list_from_file(); refresh(); init_curses(); display_list(); } string get_resultfile(void) { if(opt_resultfile.size() >0) { return opt_resultfile; } return string(DefaultResultfile); } char* get_cwd_as_charp(void) { char buf[PATH_MAX]; char* result = getcwd(buf, sizeof(buf)); if(result == NULL) { message("cannot determine current working directory.exit."); sleep(1); abort_cdargs(); } /* this can be a memleak if not freed again */ /* but then ... how long does cdargs usually run? */ return strdup(buf); } string get_cwd_as_string(void) { char* cwd = get_cwd_as_charp(); if(cwd == NULL) { return ""; } else { string result = string(cwd); free(cwd); return result; } } string get_listfile(void) { string user="~"; string file=DefaultListfile; string effective_listfile = ""; if(opt_user.size() > 0) { user = opt_user; } if(opt_listfile.size() > 0) { return canonify_filename(opt_listfile); } if(user[0] == '~') { effective_listfile = user + "/" + file; } else { effective_listfile = "/home/" + user + "/" + file; } return canonify_filename(effective_listfile); } // return the last dir in path with a capital initial letter string capitalized_last_dirname(string path) { string dirname = path.substr(path.find_last_of('/') + 1); dirname[0] = toupper(dirname[0]); return dirname; } // same but not capitalized string last_dirname(string path) { // trailing slash if(path.at(path.size()-1) == '/') { path = path.substr(0,path.size()-1); } string dirname = path.substr(path.find_last_of('/') + 1); return dirname; } string canonify_filename(string filename) { size_t pos=0; // remove double slashes if((pos = filename.find("//",0)) < filename.size()) { filename.replace(pos,2,"/"); } if (filename[0] != '~') { return filename; } // resolve home directory return string(getenv("HOME")) + filename.substr(1); } void version(void) { printf("cdargs - version %s\n", VERSION); } void usage(void) { printf("expanding the shell built-in cd with bookmarks and browser\n\n"); printf(" Usually you don't call this programm directly.\n"); printf(" Copy it somewhere into your path and create\n"); printf(" a shell alias (which takes arguments) as described\n"); printf(" in the file INSTALL which came with this program.\n"); printf("\n For general usage press 'H' while running.\n\n\n"); printf("Usage:\n"); printf(" cdargs select from bookmarks\n"); printf(" cdargs [--noresolve] \n"); printf(" Needle is a filter for the list: each\n"); printf(" entry is compared to the Needle and if it\n"); printf(" doesn't match it won't show up.\n"); printf(" The Needle performs some magic here. See\n"); printf(" manpage for details.\n"); printf(" If --noresolve is given the list will be shown\n"); printf(" even if Needle matches a description exactly.\n"); printf(" cdargs Needle is a digit: make digit the current entry\n\n"); printf(" cdargs [-a|--add]=[:desc:]path \n"); printf(" add PATH to the list file using the\n"); printf(" optional description between the colons\n"); printf(" and exit\n"); printf("\n"); printf("Other Options\n"); printf(" -f, --file=PATH take PATH as bookmark file\n"); printf(" -u, --user=USER read (default) bookmark file of USER\n"); printf(" -o,- -output=FILE use FILE as result file\n"); printf(" -r, --nowrap change the scrolling behaviour for long lists\n"); printf(" -c, --cwd make current directory the current entry if on the list\n"); printf(" -b, --browse start in BROWSE mode with the current dir\n"); printf(" -h, --help print this help message and exit\n"); printf(" -v, --version print version info and exit\n"); printf("\n"); } /***********************************************************/ /* CURSES STUFF */ /***********************************************************/ /* stuff to initialise the ncurses lib */ void init_curses(void) { initscr(); // init curses screen //savetty(); // ?? nonl(); // no newline cbreak(); // not in cooked mode: char immediately available noecho(); // chars are not echoed keypad(stdscr, true); // Arrow keys etc leaveok(stdscr, TRUE); // Don't show the... curs_set(0); // ...cursor set_areas(); curses_running = true; } void message(const char* msg) { if (curses_running) { move(msg_area_y, 0); clrtoeol(); printw("%s [any key to continue]", msg); refresh(); getch(); move(msg_area_y, 0); clrtoeol(); } else { printf("%s\n", msg); } } void display_list(void) { char description_format[40]; vector > list; if (mode == LIST) { // perform some magic here: if the list contains just one // entry (probably due to filtering by giving a Needle) // we are done. if(default_list.size() == 1 && !opt_no_resolve) { finish(current_entry(), true); } else { list = default_list; } } else { list = cur_list; } clear(); update_modeline(); int pos = display_area_ymin; if(CurrPosition > static_cast(list.size()) - 1) { CurrPosition = list.size()-1; } // Calculate actual maxlength of descriptions so we can eliminate // trailing blanks. We have to iterate thru the list to get the // longest description unsigned int actual_maxlength = 0; if(mode == LIST) { for (listit li = list.begin() + yoffset;li != list.end(); ++li) { if ( strlen(li->first.c_str() ) > actual_maxlength ) { actual_maxlength = strlen(li->first.c_str() ); } } // Don't let actual_maxlength > DESCRIPTION_MAXLENGTH if ( actual_maxlength > DESCRIPTION_MAXLENGTH) { actual_maxlength = DESCRIPTION_MAXLENGTH; } } else { actual_maxlength = DESCRIPTION_BROWSELENGTH; } string cwd = get_cwd_as_string(); for (listit li = list.begin() + yoffset;li != list.end(); ++li) { //string desc = li->first.substr(0, (DESCRIPTION_MAXLENGTH+1)); string desc = li->first.substr(0, actual_maxlength); string path = li->second.substr(0, xmax - 16); string fullpath = li->second; char validmarker = ' '; if(!valid(fullpath,PATH_IS_DIR)) { validmarker = '!'; } if (pos > display_area_ymax) { break; } move(pos, 0); if (pos == CurrPosition - yoffset) { attron(A_STANDOUT); } if (fullpath == cwd) { attron(A_BOLD); } if (pos >= shorties_offset && pos < 10+shorties_offset) { printw("%2d", pos-shorties_offset); } else { printw(" "); } // Compose format string for printw. Notice %% to represent literal % sprintf(description_format, " [%%-%ds] %%c%%s", actual_maxlength ); printw(description_format, desc.c_str(), validmarker,path.c_str()); if (pos == CurrPosition - yoffset) { attroff(A_STANDOUT); } if (fullpath == cwd) { attroff(A_BOLD); } pos++; } } void update_modeline(void) { move(modeliney, 0); clrtoeol(); move(modeliney, 0); attron(A_REVERSE); string curmode; char modechar; if (mode == LIST) { curmode = "List"; modechar = 'L'; } else { curmode = "Browse"; modechar = 'B'; } string cwd = get_cwd_as_string(); attron(A_BOLD); printw("%c: %s ", modechar, cwd.c_str()); // printw(" yoff: %d shoff: %d currpos: %d ", yoffset,shorties_offset,CurrPosition); attroff(A_BOLD); hline(' ', xmax); attroff(A_REVERSE); } void set_areas(void) { getmaxyx(stdscr, terminal_height, terminal_width); // get win-dims xmax = terminal_width - 1; display_area_ymin = 0; display_area_ymax = terminal_height - 3; modeliney = terminal_height - 2; msg_area_y = terminal_height - 1; } void resizeevent(int sig) { // shut up, compiler (void)sig; // re-connect signal(SIGWINCH, resizeevent); // FIXME: is this the correct way?? endwin(); refresh(); init_curses(); display_list(); } bool visible(int pos) { if(pos < 0) { return false; } if(pos-yoffset<0) { return false; } if(pos+yoffset > display_area_ymax) { return false; } return true; } int max_yoffset(void) { int len, ret; if (mode == BROWSE) { len = cur_list.size() - 1; } else { len = default_list.size() - 1; } ret = len - display_area_ymax; return ret<0 ? 0:ret; } void helpscreen(void) { char *pager = getenv("PAGER"); if (!pager) { // FIXME: how to detect debian and the /usr/bin/pager rule? struct stat buf; if(stat("/usr/bin/pager",&buf) == 0) { pager = "/usr/bin/pager"; } else { if(stat("/usr/bin/less",&buf) == 0) { pager = "/usr/bin/less"; } else { pager = "more"; } } } endwin(); FILE* help = popen(pager,"w"); // FIXME: error checking this doesn't work, er? if(help == NULL) { init_curses(); message("could not open pager"); return; } int l = 0; char* help_lines[] = { "cdargs (c) 2001-2003 S. Kamphausen ", "/ move selection up/down and scroll", " please see manpage for alternative bindings!", " select current entry", " toggle modes: LIST or BROWSE", "/ goto first/last entry in list if available in terminal", "c add current directory to list", "C same as 'c' but ask for a description", " scroll the list 10 lines up or down", "e,v edit the list in $EDITOR", "H,? show this help", "~,/ browse home/root directory", "q quit - saving the list", "C-c,C-g,C-[ abort - don't save the list", " ", "Commands in BROWSE-mode:", " descent into current directory", " up one directory", "[num] make [num] the highlighted entry", "a add current entry to list", "A same as 'a' but ask for a description", ". toggle display of hidden files", " ", "Commands in LIST-mode:", "[num] select and resolve entry [num] if displayed", " descent into the current entry", " up one directory from current dir", "d delete current entry from list", "a add current directory to list", "s,t swap(transpose) two lines in the list", "M/m move an entry up down in the list" }; int help_lines_size = 31; while (l < help_lines_size) { fprintf(help,"%s\n",help_lines[l]); l++; } fprintf(help,"\n"); pclose(help); // convenience for more which exits at the end: if(strstr(pager,"more")) { sleep(1); } refresh(); init_curses(); } /************************************************/ /* EXITs */ /************************************************/ void fatal_exit(char* msg) { endwin(); fprintf(stderr, msg); exit(1); } void terminate(int sig) { endwin(); if(sig == 11) { fprintf(stderr,"programm received signal %d\n",sig); fprintf(stderr,"This should never happen.\n"); fprintf(stderr,"Maybe you want to send an email to the author and report a bug?\n"); fprintf(stderr,"Author: Stefan Kamphausen \n"); } fprintf(stderr,"abort.\n"); exit(1); } void finish(string result, bool retval) { curs_set(1); // re-show cursor (FIXME: necessary?) clear(); // clear screen for stupid terminals like aterm refresh(); // ..and make sure we really see the clean screen //resetty(); // ?? endwin(); // finish the curses at all // only save if list was not filtered! if (!NeedleGiven){ list_to_file(); } // is the result really a dir? if(!valid(result,PATH_IS_DIR)) { fprintf(stderr,"This is not a valid directory:\n%s\n",result.c_str()); exit(-3); } // string resfile = canonify_filename(Resultfile); string resfile = canonify_filename(get_resultfile()); ofstream out(resfile.c_str()); if (out) { out << result << endl; out.close(); } if (!retval) { exit(1); } exit(0); } void abort_cdargs(void) { curs_set(1); // re-show cursor (FIXME: necessary?) clear(); // clear screen for stupid terminals like aterm refresh(); // ..and make sure we really see the clean screen //resetty(); // ?? endwin(); // finish the curses at all exit(-1); } bool valid(string path, pathtype mode) { struct stat buf; string canon_path = canonify_filename(path); if(mode == PATH_IS_FILE) { stat(canon_path.c_str(), &buf); if (S_ISREG(buf.st_mode)) return true; } else if (mode == PATH_IS_DIR) { stat(canon_path.c_str(), &buf); if (S_ISDIR(buf.st_mode)) return true; } return false; }