1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170
|
/*
* Copyright (c) 2000, 2001, 2002, 2003, 2004 by Martin C. Shepherd.
*
* All rights reserved.
*
* 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, and/or sell copies of the Software, and to permit persons
* to whom the Software is furnished to do so, provided that the above
* copyright notice(s) and this permission notice appear in all copies of
* the Software and that both the above copyright notice(s) and this
* permission notice appear in supporting documentation.
*
* 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
* OF THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
* HOLDERS INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL
* INDIRECT OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING
* FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
* NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
* WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*
* Except as contained in this notice, the name of a copyright holder
* shall not be used in advertising or otherwise to promote the sale, use
* or other dealings in this Software without prior written authorization
* of the copyright holder.
*/
/*
* Standard includes.
*/
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
/*
* Local includes.
*/
#include "libtecla.h"
#include "ioutil.h"
#include "stringrp.h"
#include "pathutil.h"
#include "cplfile.h"
#include "cplmatch.h"
#include "errmsg.h"
/*
* Specify the number of strings to allocate when the string free-list
* is exhausted. This also sets the number of elements to expand the
* matches[] array by whenever it is found to be too small.
*/
#define STR_BLK_FACT 100
/*
* Set the default number of spaces place between columns when listing
* a set of completions.
*/
#define CPL_COL_SEP 2
/*
* Completion matches are recorded in containers of the following
* type.
*/
struct WordCompletion {
ErrMsg *err; /* The error reporting buffer */
StringGroup *sg; /* Memory for a group of strings */
int matches_dim; /* The allocated size of result.matches[] */
CplMatches result; /* Completions to be returned to the caller */
#ifndef WITHOUT_FILE_SYSTEM
CompleteFile *cf; /* The resources used for filename completion */
#endif
};
static void cpl_sort_matches(WordCompletion *cpl);
static void cpl_zap_duplicates(WordCompletion *cpl);
static void cpl_clear_completions(WordCompletion *cpl);
static int cpl_cmp_matches(const void *v1, const void *v2);
static int cpl_cmp_suffixes(const void *v1, const void *v2);
/*
* The new_CplFileConf() constructor sets the integer first member of
* the returned object to the following magic number. On seeing this,
* cpl_file_completions() knows when it is passed a valid CplFileConf
* object.
*/
#define CFC_ID_CODE 4568
#ifndef WITHOUT_FILE_SYSTEM
/*
* A pointer to a structure of the following type can be passed to
* the builtin file-completion callback function to modify its behavior.
*/
struct CplFileConf {
int id; /* new_CplFileConf() sets this to CFC_ID_CODE */
int escaped; /* If none-zero, backslashes in the input line are */
/* interpreted as escaping special characters and */
/* spaces, and any special characters and spaces in */
/* the listed completions will also be escaped with */
/* added backslashes. This is the default behaviour. */
/* If zero, backslashes are interpreted as being */
/* literal parts of the filename, and none are added */
/* to the completion suffixes. */
int file_start; /* The index in the input line of the first character */
/* of the filename. If you specify -1 here, */
/* cpl_file_completions() identifies the */
/* the start of the filename by looking backwards for */
/* an unescaped space, or the beginning of the line. */
CplCheckFn *chk_fn; /* If not zero, this argument specifies a */
/* function to call to ask whether a given */
/* file should be included in the list */
/* of completions. */
void *chk_data; /* Anonymous data to be passed to check_fn(). */
};
static void cpl_init_FileConf(CplFileConf *cfc);
/*
* When file-system access is being excluded, define a dummy structure
* to satisfy the typedef in libtecla.h.
*/
#else
struct CplFileConf {int dummy;};
#endif
/*
* Encapsulate the formatting information needed to layout a
* multi-column listing of completions.
*/
typedef struct {
int term_width; /* The width of the terminal (characters) */
int column_width; /* The number of characters within in each column. */
int ncol; /* The number of columns needed */
int nline; /* The number of lines needed */
} CplListFormat;
/*
* Given the current terminal width, and a list of completions, determine
* how to best use the terminal width to display a multi-column listing
* of completions.
*/
static void cpl_plan_listing(CplMatches *result, int term_width,
CplListFormat *fmt);
/*
* Display a given line of a multi-column list of completions.
*/
static int cpl_format_line(CplMatches *result, CplListFormat *fmt, int lnum,
GlWriteFn *write_fn, void *data);
/*.......................................................................
* Create a new string-completion object.
*
* Output:
* return WordCompletion * The new object, or NULL on error.
*/
WordCompletion *new_WordCompletion(void)
{
WordCompletion *cpl; /* The object to be returned */
/*
* Allocate the container.
*/
cpl = (WordCompletion *) malloc(sizeof(WordCompletion));
if(!cpl) {
errno = ENOMEM;
return NULL;
};
/*
* Before attempting any operation that might fail, initialize the
* container at least up to the point at which it can safely be passed
* to del_WordCompletion().
*/
cpl->err = NULL;
cpl->sg = NULL;
cpl->matches_dim = 0;
cpl->result.suffix = NULL;
cpl->result.cont_suffix = NULL;
cpl->result.matches = NULL;
cpl->result.nmatch = 0;
#ifndef WITHOUT_FILE_SYSTEM
cpl->cf = NULL;
#endif
/*
* Allocate a place to record error messages.
*/
cpl->err = _new_ErrMsg();
if(!cpl->err)
return del_WordCompletion(cpl);
/*
* Allocate an object that allows a group of strings to be allocated
* efficiently by placing many of them in contiguous string segments.
*/
#ifdef WITHOUT_FILE_SYSTEM
cpl->sg = _new_StringGroup(MAX_PATHLEN_FALLBACK);
#else
cpl->sg = _new_StringGroup(_pu_pathname_dim());
#endif
if(!cpl->sg)
return del_WordCompletion(cpl);
/*
* Allocate an array for matching completions. This will be extended later
* if needed.
*/
cpl->matches_dim = STR_BLK_FACT;
cpl->result.matches = (CplMatch *) malloc(sizeof(cpl->result.matches[0]) *
cpl->matches_dim);
if(!cpl->result.matches) {
errno = ENOMEM;
return del_WordCompletion(cpl);
};
/*
* Allocate a filename-completion resource object.
*/
#ifndef WITHOUT_FILE_SYSTEM
cpl->cf = _new_CompleteFile();
if(!cpl->cf)
return del_WordCompletion(cpl);
#endif
return cpl;
}
/*.......................................................................
* Delete a string-completion object.
*
* Input:
* cpl WordCompletion * The object to be deleted.
* Output:
* return WordCompletion * The deleted object (always NULL).
*/
WordCompletion *del_WordCompletion(WordCompletion *cpl)
{
if(cpl) {
cpl->err = _del_ErrMsg(cpl->err);
cpl->sg = _del_StringGroup(cpl->sg);
if(cpl->result.matches) {
free(cpl->result.matches);
cpl->result.matches = NULL;
#ifndef WITHOUT_FILE_SYSTEM
cpl->cf = _del_CompleteFile(cpl->cf);
#endif
};
free(cpl);
};
return NULL;
}
/*.......................................................................
* This function is designed to be called by CplMatchFn callback
* functions. It adds one possible completion of the token that is being
* completed to an array of completions. If the completion needs any
* special quoting to be valid when displayed in the input line, this
* quoting must be included in the string.
*
* Input:
* cpl WordCompletion * The argument of the same name that was passed
* to the calling CplMatchFn callback function.
* line const char * The input line, as received by the callback
* function.
* word_start int The index within line[] of the start of the
* word that is being completed.
* word_end int The index within line[] of the character which
* follows the incomplete word, as received by the
* calling callback function.
* suffix const char * The appropriately quoted string that could
* be appended to the incomplete token to complete
* it. A copy of this string will be allocated
* internally.
* type_suffix const char * When listing multiple completions, gl_get_line()
* appends this string to the completion to indicate
* its type to the user. If not pertinent pass "".
* Otherwise pass a literal or static string.
* cont_suffix const char * If this turns out to be the only completion,
* gl_get_line() will append this string as
* a continuation. For example, the builtin
* file-completion callback registers a directory
* separator here for directory matches, and a
* space otherwise. If the match were a function
* name you might want to append an open
* parenthesis, etc.. If not relevant pass "".
* Otherwise pass a literal or static string.
* Output:
* return int 0 - OK.
* 1 - Error.
*/
int cpl_add_completion(WordCompletion *cpl, const char *line,
int word_start, int word_end, const char *suffix,
const char *type_suffix, const char *cont_suffix)
{
CplMatch *match; /* The container of the new match */
char *string; /* A newly allocated copy of the completion string */
/*
* Check the arguments.
*/
if(!cpl)
return 1;
if(!suffix)
return 0;
if(!type_suffix)
type_suffix = "";
if(!cont_suffix)
cont_suffix = "";
/*
* Do we need to extend the array of matches[]?
*/
if(cpl->result.nmatch+1 > cpl->matches_dim) {
int needed = cpl->matches_dim + STR_BLK_FACT;
CplMatch *matches = (CplMatch *) realloc(cpl->result.matches,
sizeof(cpl->result.matches[0]) * needed);
if(!matches) {
_err_record_msg(cpl->err,
"Insufficient memory to extend array of matches.",
END_ERR_MSG);
return 1;
};
cpl->result.matches = matches;
cpl->matches_dim = needed;
};
/*
* Allocate memory to store the combined completion prefix and the
* new suffix.
*/
string = _sg_alloc_string(cpl->sg, word_end-word_start + strlen(suffix));
if(!string) {
_err_record_msg(cpl->err, "Insufficient memory to extend array of matches.",
END_ERR_MSG);
return 1;
};
/*
* Compose the string.
*/
strncpy(string, line + word_start, word_end - word_start);
strcpy(string + word_end - word_start, suffix);
/*
* Record the new match.
*/
match = cpl->result.matches + cpl->result.nmatch++;
match->completion = string;
match->suffix = string + word_end - word_start;
match->type_suffix = type_suffix;
/*
* Record the continuation suffix.
*/
cpl->result.cont_suffix = cont_suffix;
return 0;
}
/*.......................................................................
* Sort the array of matches.
*
* Input:
* cpl WordCompletion * The completion resource object.
*/
static void cpl_sort_matches(WordCompletion *cpl)
{
qsort(cpl->result.matches, cpl->result.nmatch,
sizeof(cpl->result.matches[0]), cpl_cmp_matches);
}
/*.......................................................................
* This is a qsort() comparison function used to sort matches.
*
* Input:
* v1, v2 void * Pointers to the two matches to be compared.
* Output:
* return int -1 -> v1 < v2.
* 0 -> v1 == v2
* 1 -> v1 > v2
*/
static int cpl_cmp_matches(const void *v1, const void *v2)
{
const CplMatch *m1 = (const CplMatch *) v1;
const CplMatch *m2 = (const CplMatch *) v2;
return strcmp(m1->completion, m2->completion);
}
/*.......................................................................
* Sort the array of matches in order of their suffixes.
*
* Input:
* cpl WordCompletion * The completion resource object.
*/
static void cpl_sort_suffixes(WordCompletion *cpl)
{
qsort(cpl->result.matches, cpl->result.nmatch,
sizeof(cpl->result.matches[0]), cpl_cmp_suffixes);
}
/*.......................................................................
* This is a qsort() comparison function used to sort matches in order of
* their suffixes.
*
* Input:
* v1, v2 void * Pointers to the two matches to be compared.
* Output:
* return int -1 -> v1 < v2.
* 0 -> v1 == v2
* 1 -> v1 > v2
*/
static int cpl_cmp_suffixes(const void *v1, const void *v2)
{
const CplMatch *m1 = (const CplMatch *) v1;
const CplMatch *m2 = (const CplMatch *) v2;
return strcmp(m1->suffix, m2->suffix);
}
/*.......................................................................
* Find the common prefix of all of the matching completion matches,
* and record a pointer to it in cpl->result.suffix. Note that this has
* the side effect of sorting the matches into suffix order.
*
* Input:
* cpl WordCompletion * The completion resource object.
* Output:
* return int 0 - OK.
* 1 - Error.
*/
static int cpl_common_suffix(WordCompletion *cpl)
{
CplMatches *result; /* The result container */
const char *first, *last; /* The first and last matching suffixes */
int length; /* The length of the common suffix */
/*
* Get the container of the array of matching files.
*/
result = &cpl->result;
/*
* No matching completions?
*/
if(result->nmatch < 1)
return 0;
/*
* Sort th matches into suffix order.
*/
cpl_sort_suffixes(cpl);
/*
* Given that the array of matches is sorted, the first and last
* suffixes are those that differ most in their prefixes, so the common
* prefix of these strings is the longest common prefix of all of the
* suffixes.
*/
first = result->matches[0].suffix;
last = result->matches[result->nmatch - 1].suffix;
/*
* Find the point at which the first and last matching strings
* first difffer.
*/
while(*first && *first == *last) {
first++;
last++;
};
/*
* How long is the common suffix?
*/
length = first - result->matches[0].suffix;
/*
* Allocate memory to record the common suffix.
*/
result->suffix = _sg_alloc_string(cpl->sg, length);
if(!result->suffix) {
_err_record_msg(cpl->err,
"Insufficient memory to record common completion suffix.",
END_ERR_MSG);
return 1;
};
/*
* Record the common suffix.
*/
strncpy(result->suffix, result->matches[0].suffix, length);
result->suffix[length] = '\0';
return 0;
}
/*.......................................................................
* Discard the contents of the array of possible completion matches.
*
* Input:
* cpl WordCompletion * The word-completion resource object.
*/
static void cpl_clear_completions(WordCompletion *cpl)
{
/*
* Discard all of the strings.
*/
_clr_StringGroup(cpl->sg);
/*
* Record the fact that the array is now empty.
*/
cpl->result.nmatch = 0;
cpl->result.suffix = NULL;
cpl->result.cont_suffix = "";
/*
* Also clear the error message.
*/
_err_clear_msg(cpl->err);
return;
}
/*.......................................................................
* Given an input line and the point at which it completion is to be
* attempted, return an array of possible completions.
*
* Input:
* cpl WordCompletion * The completion resource object.
* line char * The current input line.
* word_end int The index of the character in line[] which
* follows the end of the token that is being
* completed.
* data void * Anonymous 'data' to be passed to match_fn().
* match_fn CplMatchFn * The function that will identify the prefix
* to be completed from the input line, and
* record completion matches.
* Output:
* return CplMatches * The container of the array of possible
* completions. The returned pointer refers
* to a container owned by the parent WordCompletion
* object, and its contents thus potentially
* change on every call to cpl_matches().
* On error, NULL is returned, and a description
* of the error can be acquired by calling
* cpl_last_error(cpl).
*/
CplMatches *cpl_complete_word(WordCompletion *cpl, const char *line,
int word_end, void *data,
CplMatchFn *match_fn)
{
int line_len; /* The total length of the input line */
/*
* How long is the input line?
*/
line_len = strlen(line);
/*
* Check the arguments.
*/
if(!cpl || !line || !match_fn || word_end < 0 || word_end > line_len) {
if(cpl) {
_err_record_msg(cpl->err, "cpl_complete_word: Invalid arguments.",
END_ERR_MSG);
};
return NULL;
};
/*
* Clear the return container.
*/
cpl_clear_completions(cpl);
/*
* Have the matching function record possible completion matches in
* cpl->result.matches.
*/
if(match_fn(cpl, data, line, word_end)) {
if(_err_get_msg(cpl->err)[0] == '\0')
_err_record_msg(cpl->err, "Error completing word.", END_ERR_MSG);
return NULL;
};
/*
* Record a copy of the common initial part of all of the prefixes
* in cpl->result.common.
*/
if(cpl_common_suffix(cpl))
return NULL;
/*
* Sort the matches into lexicographic order.
*/
cpl_sort_matches(cpl);
/*
* Discard any duplicate matches.
*/
cpl_zap_duplicates(cpl);
/*
* If there is more than one match, discard the continuation suffix.
*/
if(cpl->result.nmatch > 1)
cpl->result.cont_suffix = "";
/*
* Return the array of matches.
*/
return &cpl->result;
}
/*.......................................................................
* Recall the return value of the last call to cpl_complete_word().
*
* Input:
* cpl WordCompletion * The completion resource object.
* Output:
* return CplMatches * The container of the array of possible
* completions, as returned by the last call to
* cpl_complete_word(). The returned pointer refers
* to a container owned by the parent WordCompletion
* object, and its contents thus potentially
* change on every call to cpl_complete_word().
* On error, either in the execution of this
* function, or in the last call to
* cpl_complete_word(), NULL is returned, and a
* description of the error can be acquired by
* calling cpl_last_error(cpl).
*/
CplMatches *cpl_recall_matches(WordCompletion *cpl)
{
return (!cpl || *_err_get_msg(cpl->err)!='\0') ? NULL : &cpl->result;
}
/*.......................................................................
* Print out an array of matching completions.
*
* Input:
* result CplMatches * The container of the sorted array of
* completions.
* fp FILE * The output stream to write to.
* term_width int The width of the terminal.
* Output:
* return int 0 - OK.
* 1 - Error.
*/
int cpl_list_completions(CplMatches *result, FILE *fp, int term_width)
{
return _cpl_output_completions(result, _io_write_stdio, fp, term_width);
}
/*.......................................................................
* Print an array of matching completions via a callback function.
*
* Input:
* result CplMatches * The container of the sorted array of
* completions.
* write_fn GlWriteFn * The function to call to write the completions,
* or 0 to discard the output.
* data void * Anonymous data to pass to write_fn().
* term_width int The width of the terminal.
* Output:
* return int 0 - OK.
* 1 - Error.
*/
int _cpl_output_completions(CplMatches *result, GlWriteFn *write_fn, void *data,
int term_width)
{
CplListFormat fmt; /* List formatting information */
int lnum; /* The sequential number of the line to print next */
/*
* Not enough space to list anything?
*/
if(term_width < 1)
return 0;
/*
* Do we have a callback to write via, and any completions to be listed?
*/
if(write_fn && result && result->nmatch>0) {
/*
* Work out how to arrange the listing into fixed sized columns.
*/
cpl_plan_listing(result, term_width, &fmt);
/*
* Print the listing via the specified callback.
*/
for(lnum=0; lnum < fmt.nline; lnum++) {
if(cpl_format_line(result, &fmt, lnum, write_fn, data))
return 1;
};
};
return 0;
}
/*.......................................................................
* Return a description of the string-completion error that occurred.
*
* Input:
* cpl WordCompletion * The string-completion resource object.
* Output:
* return const char * The description of the last error.
*/
const char *cpl_last_error(WordCompletion *cpl)
{
return cpl ? _err_get_msg(cpl->err) : "NULL WordCompletion argument";
}
/*.......................................................................
* When an error occurs while performing a completion, you registerf a
* terse description of the error by calling cpl_record_error(). This
* message will then be returned on the next call to cpl_last_error().
*
* Input:
* cpl WordCompletion * The string-completion resource object that was
* originally passed to the callback.
* errmsg const char * The description of the error.
*/
void cpl_record_error(WordCompletion *cpl, const char *errmsg)
{
if(cpl && errmsg)
_err_record_msg(cpl->err, errmsg, END_ERR_MSG);
}
/*.......................................................................
* This is the builtin completion callback function which performs file
* completion.
*
* Input:
* cpl WordCompletion * An opaque pointer to the object that will
* contain the matches. This should be filled
* via zero or more calls to cpl_add_completion().
* data void * Either NULL to request the default
* file-completion behavior, or a pointer to a
* CplFileConf structure, whose members specify
* a different behavior.
* line char * The current input line.
* word_end int The index of the character in line[] which
* follows the end of the token that is being
* completed.
* Output
* return int 0 - OK.
* 1 - Error.
*/
CPL_MATCH_FN(cpl_file_completions)
{
#ifdef WITHOUT_FILE_SYSTEM
return 0;
#else
const char *start_path; /* The pointer to the start of the pathname */
/* in line[]. */
CplFileConf *conf; /* The new-style configuration object. */
/*
* The following configuration object will be used if the caller didn't
* provide one.
*/
CplFileConf default_conf;
/*
* This function can be called externally, so check its arguments.
*/
if(!cpl)
return 1;
if(!line || word_end < 0) {
_err_record_msg(cpl->err, "cpl_file_completions: Invalid arguments.",
END_ERR_MSG);
return 1;
};
/*
* The 'data' argument is either a CplFileConf pointer, identifiable
* by having an integer id code as its first member, or the deprecated
* CplFileArgs pointer, or can be NULL to request the default
* configuration.
*/
if(data && *(int *)data == CFC_ID_CODE) {
conf = (CplFileConf *) data;
} else {
/*
* Select the defaults.
*/
conf = &default_conf;
cpl_init_FileConf(&default_conf);
/*
* If we have been passed an instance of the deprecated CplFileArgs
* structure, copy its configuration parameters over the defaults.
*/
if(data) {
CplFileArgs *args = (CplFileArgs *) data;
conf->escaped = args->escaped;
conf->file_start = args->file_start;
};
};
/*
* Get the start of the filename. If not specified by the caller
* identify it by searching backwards in the input line for an
* unescaped space or the start of the line.
*/
if(conf->file_start < 0) {
start_path = _pu_start_of_path(line, word_end);
if(!start_path) {
_err_record_msg(cpl->err, "Unable to find the start of the filename.",
END_ERR_MSG);
return 1;
};
} else {
start_path = line + conf->file_start;
};
/*
* Perform the completion.
*/
if(_cf_complete_file(cpl, cpl->cf, line, start_path - line, word_end,
conf->escaped, conf->chk_fn, conf->chk_data)) {
cpl_record_error(cpl, _cf_last_error(cpl->cf));
return 1;
};
return 0;
#endif
}
/*.......................................................................
* Initialize a CplFileArgs structure with default configuration
* parameters. Note that the CplFileArgs configuration type is
* deprecated. The opaque CplFileConf object should be used in future
* applications.
*
* Input:
* cfa CplFileArgs * The configuration object of the
* cpl_file_completions() callback.
*/
void cpl_init_FileArgs(CplFileArgs *cfa)
{
if(cfa) {
cfa->escaped = 1;
cfa->file_start = -1;
};
}
#ifndef WITHOUT_FILE_SYSTEM
/*.......................................................................
* Initialize a CplFileConf structure with default configuration
* parameters.
*
* Input:
* cfc CplFileConf * The configuration object of the
* cpl_file_completions() callback.
*/
static void cpl_init_FileConf(CplFileConf *cfc)
{
if(cfc) {
cfc->id = CFC_ID_CODE;
cfc->escaped = 1;
cfc->file_start = -1;
cfc->chk_fn = 0;
cfc->chk_data = NULL;
};
}
#endif
/*.......................................................................
* Create a new CplFileConf object and initialize it with defaults.
*
* Output:
* return CplFileConf * The new object, or NULL on error.
*/
CplFileConf *new_CplFileConf(void)
{
#ifdef WITHOUT_FILE_SYSTEM
errno = EINVAL;
return NULL;
#else
CplFileConf *cfc; /* The object to be returned */
/*
* Allocate the container.
*/
cfc = (CplFileConf *)malloc(sizeof(CplFileConf));
if(!cfc)
return NULL;
/*
* Before attempting any operation that might fail, initialize the
* container at least up to the point at which it can safely be passed
* to del_CplFileConf().
*/
cpl_init_FileConf(cfc);
return cfc;
#endif
}
/*.......................................................................
* Delete a CplFileConf object.
*
* Input:
* cfc CplFileConf * The object to be deleted.
* Output:
* return CplFileConf * The deleted object (always NULL).
*/
CplFileConf *del_CplFileConf(CplFileConf *cfc)
{
#ifndef WITHOUT_FILE_SYSTEM
if(cfc) {
/*
* Delete the container.
*/
free(cfc);
};
#endif
return NULL;
}
/*.......................................................................
* If backslashes in the filename should be treated as literal
* characters, call the following function with literal=1. Otherwise
* the default is to treat them as escape characters, used for escaping
* spaces etc..
*
* Input:
* cfc CplFileConf * The cpl_file_completions() configuration object
* to be configured.
* literal int Pass non-zero here to enable literal interpretation
* of backslashes. Pass 0 to turn off literal
* interpretation.
*/
void cfc_literal_escapes(CplFileConf *cfc, int literal)
{
#ifndef WITHOUT_FILE_SYSTEM
if(cfc)
cfc->escaped = !literal;
#endif
}
/*.......................................................................
* Call this function if you know where the index at which the
* filename prefix starts in the input line. Otherwise by default,
* or if you specify start_index to be -1, the filename is taken
* to start after the first unescaped space preceding the cursor,
* or the start of the line, which ever comes first.
*
* Input:
* cfc CplFileConf * The cpl_file_completions() configuration object
* to be configured.
* start_index int The index of the start of the filename in
* the input line, or -1 to select the default.
*/
void cfc_file_start(CplFileConf *cfc, int start_index)
{
#ifndef WITHOUT_FILE_SYSTEM
if(cfc)
cfc->file_start = start_index;
#endif
}
/*.......................................................................
* If you only want certain types of files to be included in the
* list of completions, you use the following function to specify a
* callback function which will be called to ask whether a given file
* should be included.
*
* Input:
* cfc CplFileConf * The cpl_file_completions() configuration object
* to be configured.
* chk_fn CplCheckFn * Zero to disable filtering, or a pointer to a
* function that returns 1 if a given file should
* be included in the list of completions.
* chk_data void * Anonymous data to be passed to chk_fn()
* every time that it is called.
*/
void cfc_set_check_fn(CplFileConf *cfc, CplCheckFn *chk_fn, void *chk_data)
{
#ifndef WITHOUT_FILE_SYSTEM
if(cfc) {
cfc->chk_fn = chk_fn;
cfc->chk_data = chk_data;
};
#endif
}
/*.......................................................................
* The following CplCheckFn callback returns non-zero if the specified
* filename is that of an executable.
*/
CPL_CHECK_FN(cpl_check_exe)
{
#ifdef WITHOUT_FILE_SYSTEM
return 0;
#else
return _pu_path_is_exe(pathname);
#endif
}
/*.......................................................................
* Remove duplicates from a sorted array of matches.
*
* Input:
* cpl WordCompletion * The completion resource object.
*/
static void cpl_zap_duplicates(WordCompletion *cpl)
{
CplMatch *matches; /* The array of matches */
int nmatch; /* The number of elements in matches[] */
const char *completion; /* The completion string of the last unique match */
const char *type_suffix; /* The type of the last unique match */
int src; /* The index of the match being considered */
int dst; /* The index at which to record the next */
/* unique match. */
/*
* Get the array of matches and the number of matches that it
* contains.
*/
matches = cpl->result.matches;
nmatch = cpl->result.nmatch;
/*
* No matches?
*/
if(nmatch < 1)
return;
/*
* Initialize the comparison strings with the first match.
*/
completion = matches[0].completion;
type_suffix = matches[0].type_suffix;
/*
* Go through the array of matches, copying each new unrecorded
* match at the head of the array, while discarding duplicates.
*/
for(src=dst=1; src<nmatch; src++) {
CplMatch *match = matches + src;
if(strcmp(completion, match->completion) != 0 ||
strcmp(type_suffix, match->type_suffix) != 0) {
if(src != dst)
matches[dst] = *match;
dst++;
completion = match->completion;
type_suffix = match->type_suffix;
};
};
/*
* Record the number of unique matches that remain.
*/
cpl->result.nmatch = dst;
return;
}
/*.......................................................................
* Work out how to arrange a given array of completions into a listing
* of one or more fixed size columns.
*
* Input:
* result CplMatches * The set of completions to be listed.
* term_width int The width of the terminal. A lower limit of
* zero is quietly enforced.
* Input/Output:
* fmt CplListFormat * The formatting information will be assigned
* to the members of *fmt.
*/
static void cpl_plan_listing(CplMatches *result, int term_width,
CplListFormat *fmt)
{
int maxlen; /* The length of the longest matching string */
int i;
/*
* Ensure that term_width >= 0.
*/
if(term_width < 0)
term_width = 0;
/*
* Start by assuming the worst case, that either nothing will fit
* on the screen, or that there are no matches to be listed.
*/
fmt->term_width = term_width;
fmt->column_width = 0;
fmt->nline = fmt->ncol = 0;
/*
* Work out the maximum length of the matching strings.
*/
maxlen = 0;
for(i=0; i<result->nmatch; i++) {
CplMatch *match = result->matches + i;
int len = strlen(match->completion) + strlen(match->type_suffix);
if(len > maxlen)
maxlen = len;
};
/*
* Nothing to list?
*/
if(maxlen == 0)
return;
/*
* Split the available terminal width into columns of
* maxlen + CPL_COL_SEP characters.
*/
fmt->column_width = maxlen;
fmt->ncol = fmt->term_width / (fmt->column_width + CPL_COL_SEP);
/*
* If the column width is greater than the terminal width, zero columns
* will have been selected. Set a lower limit of one column. Leave it
* up to the caller how to deal with completions who's widths exceed
* the available terminal width.
*/
if(fmt->ncol < 1)
fmt->ncol = 1;
/*
* How many lines of output will be needed?
*/
fmt->nline = (result->nmatch + fmt->ncol - 1) / fmt->ncol;
return;
}
/*.......................................................................
* Render one line of a multi-column listing of completions, using a
* callback function to pass the output to an arbitrary destination.
*
* Input:
* result CplMatches * The container of the sorted array of
* completions.
* fmt CplListFormat * Formatting information.
* lnum int The index of the line to print, starting
* from 0, and incrementing until the return
* value indicates that there is nothing more
* to be printed.
* write_fn GlWriteFn * The function to call to write the line, or
* 0 to discard the output.
* data void * Anonymous data to pass to write_fn().
* Output:
* return int 0 - Line printed ok.
* 1 - Nothing to print.
*/
static int cpl_format_line(CplMatches *result, CplListFormat *fmt, int lnum,
GlWriteFn *write_fn, void *data)
{
int col; /* The index of the list column being output */
/*
* If the line index is out of bounds, there is nothing to be written.
*/
if(lnum < 0 || lnum >= fmt->nline)
return 1;
/*
* If no output function has been provided, return as though the
* line had been printed.
*/
if(!write_fn)
return 0;
/*
* Print the matches in 'ncol' columns, sorted in line order within each
* column.
*/
for(col=0; col < fmt->ncol; col++) {
int m = col*fmt->nline + lnum;
/*
* Is there another match to be written? Note that in general
* the last line of a listing will have fewer filled columns
* than the initial lines.
*/
if(m < result->nmatch) {
CplMatch *match = result->matches + m;
/*
* How long are the completion and type-suffix strings?
*/
int clen = strlen(match->completion);
int tlen = strlen(match->type_suffix);
/*
* Write the completion string.
*/
if(write_fn(data, match->completion, clen) != clen)
return 1;
/*
* Write the type suffix, if any.
*/
if(tlen > 0 && write_fn(data, match->type_suffix, tlen) != tlen)
return 1;
/*
* If another column follows the current one, pad to its start with spaces.
*/
if(col+1 < fmt->ncol) {
/*
* The following constant string of spaces is used to pad the output.
*/
static const char spaces[] = " ";
static const int nspace = sizeof(spaces) - 1;
/*
* Pad to the next column, using as few sub-strings of the spaces[]
* array as possible.
*/
int npad = fmt->column_width + CPL_COL_SEP - clen - tlen;
while(npad>0) {
int n = npad > nspace ? nspace : npad;
if(write_fn(data, spaces + nspace - n, n) != n)
return 1;
npad -= n;
};
};
};
};
/*
* Start a new line.
*/
{
char s[] = "\r\n";
int n = strlen(s);
if(write_fn(data, s, n) != n)
return 1;
};
return 0;
}
|