diff --git a/Makefile b/Makefile index b5ca79a..13447c1 100644 --- a/Makefile +++ b/Makefile @@ -18,7 +18,7 @@ OBJS := $(SRCS:$(PROJECT_DIR)/%.c=$(OBJ_DIR)/%.o) TARGET := $(BIN_DIR)/lsr -.PHONY: all debug release clean format test install uninstall +.PHONY: all debug release clean format test install uninstall completions all: debug @@ -43,11 +43,22 @@ $(BIN_DIR) $(OBJ_DIR): clean: rm -rf $(OBJ_DIR) $(BIN_DIR) + rm -rf completions format: clang-format -i $(SRCS) $(PROJECT_DIR)/include/**.h -install: release +completions: + @mkdir -p completions + @mkdir -p completions/bash + @mkdir -p completions/zsh + @mkdir -p completions/fish + + ./$(TARGET) --completions bash > completions/bash/lsr + ./$(TARGET) --completions zsh > completions/zsh/_lsr + ./$(TARGET) --completions fish > completions/fish/lsr.fish + +install: release completions install -m 755 $(TARGET) /usr/local/bin make clean diff --git a/README.md b/README.md index 9120ff1..9b1b112 100644 --- a/README.md +++ b/README.md @@ -24,6 +24,8 @@ brew tap jmattaa/laser brew install --formula laser ``` +By installing with Homebrew, you get the shell completions for the cli as well! + ### Building from source Clone the repository: @@ -45,6 +47,12 @@ To uninstall you can run sudo make uninstall ``` +> [!NOTE] +> This dosen't install the shell completions for the cli but you can add them +> by placing the files from the `completions` directory in a directory that is in +> your `$PATH`. Or source the file from your `bashrc`, `zshrc` or `config.fish` +> file. + ## Usage After installing you can run laser in your current directory by simply diff --git a/completions/bash/lsr b/completions/bash/lsr new file mode 100644 index 0000000..2d8c86f --- /dev/null +++ b/completions/bash/lsr @@ -0,0 +1,11 @@ +_lsr() { + local cur prev opts + COMPREPLY=() + cur="${COMP_WORDS[COMP_CWORD]}" + opts="-a --all -F --Files -D --Directories -S --Symlinks -G --Git -l --long -r --recursive --completions " + if [[ ${cur} == -* ]]; then + COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) ) + fi + return 0 +} +complete -F _lsr lsr diff --git a/completions/fish/lsr.fish b/completions/fish/lsr.fish new file mode 100644 index 0000000..d5550bc --- /dev/null +++ b/completions/fish/lsr.fish @@ -0,0 +1,9 @@ +# Fish completions for lsr +complete -c lsr -s a -l all -d 'Show all entries, including hidden' +complete -c lsr -s F -l Files -d 'Show files only' +complete -c lsr -s D -l Directories -d 'Show directories only' +complete -c lsr -s S -l Symlinks -d 'Show symlinks only' +complete -c lsr -s G -l Git -d 'Show entries that are not defined in .gitignore' +complete -c lsr -s l -l long -d 'Use long format' +complete -c lsr -s r -l recursive -d 'Show in tree format' +complete -c lsr -l completions -d 'Generate shell completions' diff --git a/completions/zsh/_lsr b/completions/zsh/_lsr new file mode 100644 index 0000000..25e24f8 --- /dev/null +++ b/completions/zsh/_lsr @@ -0,0 +1,13 @@ +compdef _lsr lsr +_lsr() { + _arguments -s \ + '(-a --all)'{-a,--all}'[Show all entries, including hidden]' \ + '(-F --Files)'{-F,--Files}'[Show files only]' \ + '(-D --Directories)'{-D,--Directories}'[Show directories only]' \ + '(-S --Symlinks)'{-S,--Symlinks}'[Show symlinks only]' \ + '(-G --Git)'{-G,--Git}'[Show entries that are not defined in .gitignore]' \ + '(-l --long)'{-l,--long}'[Use long format]' \ + '(-r --recursive)'{-r,--recursive}'[Show in tree format]' \ + '--completions[Generate shell completions]' \ + '*:file:_files' +} diff --git a/include/utils.h b/include/utils.h index 59fdd78..4e794c0 100644 --- a/include/utils.h +++ b/include/utils.h @@ -25,6 +25,7 @@ typedef struct laser_opts } laser_opts; laser_opts laser_utils_parsecmd(int argc, char **argv); +void laser_generate_completions(const char *shell, struct option long_args[]); //void laser_utils_format_date(time_t time, char *buffer, size_t buffer_size); char **laser_utils_grow_strarray(char **array, size_t *alloc_size, size_t count); diff --git a/src/utils.c b/src/utils.c index f7cc700..4dc0d79 100644 --- a/src/utils.c +++ b/src/utils.c @@ -22,6 +22,7 @@ laser_opts laser_utils_parsecmd(int argc, char **argv) {"Git", 0, 0, 'G'}, {"long", 0, 0, 'l'}, {"recursive", optional_argument, 0, 'r'}, + {"completions", required_argument, 0, 0}, {0, 0, 0, 0}}; while ((opt = getopt_long(argc, argv, "aFDSGr::l", long_args, NULL)) != -1) @@ -66,6 +67,12 @@ laser_opts laser_utils_parsecmd(int argc, char **argv) case 'l': show_long = 1; break; + case 0: + if (long_args[optind - 1].flag == 0) + { + laser_generate_completions(argv[optind - 1], long_args); + exit(0); + } default: exit(1); } @@ -80,6 +87,91 @@ laser_opts laser_utils_parsecmd(int argc, char **argv) .parentDir = dir}; } +void laser_generate_completions(const char *shell, struct option long_args[]) +{ + // UPDATE THIS TO MATCH long_args!! + char *descriptions[] = {"Show all entries, including hidden", + "Show files only", + "Show directories only", + "Show symlinks only", + "Show entries that are not defined in .gitignore", + "Use long format", + "Show in tree format", + "Generate shell completions"}; + + if (strcmp(shell, "bash") == 0) + { + printf("_lsr() {\n"); + printf(" local cur prev opts\n"); + printf(" COMPREPLY=()\n"); + printf(" cur=\"${COMP_WORDS[COMP_CWORD]}\"\n"); + + printf(" opts=\""); + for (int i = 0; long_args[i].name != NULL; i++) + { + char short_flag = long_args[i].val ? long_args[i].val : '\0'; + if (short_flag) + { + printf("-%c ", short_flag); + } + printf("--%s ", long_args[i].name); + } + printf("\"\n"); + + printf(" if [[ ${cur} == -* ]]; then\n"); + printf(" COMPREPLY=( $(compgen -W \"${opts}\" -- ${cur}) )\n"); + printf(" fi\n"); + + printf(" return 0\n"); + printf("}\n"); + printf("complete -F _lsr lsr\n"); + } + else if (strcmp(shell, "zsh") == 0) + { + printf("compdef _lsr lsr\n"); + printf("_lsr() {\n"); + printf(" _arguments -s \\\n"); + for (int i = 0; long_args[i].name != NULL; i++) + { + char short_flag = long_args[i].val ? long_args[i].val : '\0'; + if (short_flag) + { + printf(" '(-%c --%s)'{-%c,--%s}'[%s]' \\\n", short_flag, + long_args[i].name, short_flag, long_args[i].name, + descriptions[i]); + } + else + { + printf(" '--%s[%s]' \\\n", long_args[i].name, + descriptions[i]); + } + } + printf(" '*:file:_files'\n"); + printf("}\n"); + } + else if (strcmp(shell, "fish") == 0) + { + printf("# Fish completions for lsr\n"); + for (int i = 0; long_args[i].name != NULL; i++) + { + printf("complete -c lsr"); + if (long_args[i].val) + { + printf(" -s %c", long_args[i].val); + } + printf(" -l %s -d '%s'\n", long_args[i].name, descriptions[i]); + } + } + else + { + fprintf(stderr, + "lsr: unsupported shell '%s'. Supported shells are bash, " + "zsh, and fish.\n", + shell); + exit(1); + } +} + void laser_utils_format_date(time_t time, char *buffer, size_t buffer_size) { struct tm *tm_info = localtime(&time);