about summary refs log tree commit diff
diff options
context:
space:
mode:
authorNakidai <nakidai@disroot.org>2025-06-11 18:47:21 +0300
committerNakidai <nakidai@disroot.org>2025-06-11 18:47:21 +0300
commitb2e86523e97b343639a74ce64063bf566c939ddd (patch)
treee030ef3f3de8bf58d3d50689c9a2cef6eeaaf7f9
downloadfp-b2e86523e97b343639a74ce64063bf566c939ddd.tar.gz
fp-b2e86523e97b343639a74ce64063bf566c939ddd.zip
Add code
-rw-r--r--.gitignore1
-rw-r--r--LICENSE10
-rw-r--r--Makefile3
-rw-r--r--README15
-rw-r--r--fp.126
-rw-r--r--fp.c167
6 files changed, 222 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..5b08531
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1 @@
+fp
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..7ae8865
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,10 @@
+Permission to use, copy, modify, and/or distribute this software for
+any purpose with or without fee is hereby granted.
+
+THE SOFTWARE IS PROVIDED “AS IS” AND THE AUTHOR DISCLAIMS ALL
+WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES
+OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE
+FOR ANY SPECIAL, DIRECT, 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.
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..f4f4cbb
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,3 @@
+all: fp
+clean:
+	rm -f fp
diff --git a/README b/README
new file mode 100644
index 0000000..81176a5
--- /dev/null
+++ b/README
@@ -0,0 +1,15 @@
+FP(1)			    General Commands Manual			 FP(1)
+
+NAME
+     fp - interactive file picker
+
+SYNOPSIS
+     fp path
+
+DESCRIPTION
+     fp searches for files from path.  User can type something and this will
+     be visible on the screen.	fp prints all the interactive stuff into
+     stderr and the result into stdout, so this means you can easily pipe this
+     program into another one.
+
+Linux 6.14.7-arch2-1		 June 11, 2025		  Linux 6.14.7-arch2-1
diff --git a/fp.1 b/fp.1
new file mode 100644
index 0000000..9e1a228
--- /dev/null
+++ b/fp.1
@@ -0,0 +1,26 @@
+.Dd June 11, 2025
+.Dt FP 1
+.Os
+.
+.Sh NAME
+.Nm fp
+.Nd interactive file picker
+.
+.Sh SYNOPSIS
+.Nm
+.Ar path
+.
+.Sh DESCRIPTION
+.Nm
+searches for files from
+.Ar path .
+User can type something
+and this will be visible on the screen.
+.Nm
+prints all the interactive stuff
+into stderr
+and the result
+into stdout,
+so this means
+you can easily pipe this program
+into another one.
diff --git a/fp.c b/fp.c
new file mode 100644
index 0000000..ac9ef2d
--- /dev/null
+++ b/fp.c
@@ -0,0 +1,167 @@
+#include <err.h>
+#include <errno.h>
+#include <limits.h>
+#include <signal.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <dirent.h>
+#include <termios.h>
+#include <unistd.h>
+
+
+struct result
+{
+	struct collision
+	{
+		size_t offset, length, fullness;
+	} c;
+	char name[NAME_MAX+1];
+} *results = 0;
+size_t amount = 0;
+struct termios state;
+
+int getch()
+{
+	int buf;
+	struct termios old;
+
+	fflush(stdout);
+
+	int res = tcgetattr(0, &old);
+	old.c_lflag &= ~ICANON & ~ECHO;
+	old.c_cc[VMIN] = 1;
+	old.c_cc[VTIME] = 0;
+	res = tcsetattr(0, TCSANOW, &old);
+	buf = getchar();
+	old.c_lflag |= ICANON | ECHO;
+	res = tcsetattr(0, TCSANOW, &old);
+	return buf;
+}
+
+void restore(void)
+{
+	tcsetattr(0, TCSANOW, &state);
+}
+
+void getchhnd(int sig)
+{
+	exit(1);
+}
+
+struct collision strsub(const char *outside, const char *inside)
+{
+	struct collision res = {0, 0};
+
+	size_t outlen = strlen(outside);
+	size_t inlen = strlen(inside);
+
+	for (size_t i = 0; i < outlen; ++i)
+	{
+		size_t j;
+		for (j = 0; j < inlen && j < outlen - i; ++j)
+			if (outside[i + j] != inside[j])
+				break;
+		if (res.length < j)
+			res.offset = i,
+			res.length = j,
+			res.fullness = outlen - j;
+	}
+	return res;
+}
+
+/* it is inverted for generating descending array */
+int colcmp(const void *_left, const void *_right)
+{
+	const struct result *left = _left, *right = _right;
+	if (left->c.length != right->c.length)
+		return left->c.length < right->c.length ? 1 : -1;
+	if (left->c.fullness != right->c.fullness)
+		return left->c.fullness < right->c.fullness ? -1 : 1;
+	if (left->c.offset != right->c.offset)
+		return left->c.offset < right->c.offset ? 1 : -1;
+	return -strcmp(left->name, right->name);
+}
+
+void loadpath(const char *path)
+{
+	errno = 0;
+	DIR *dir = opendir(path);
+	for (struct dirent *d; (d = readdir(dir));)
+	{
+		results = realloc(results, sizeof(*results) * ++amount);
+		if (!results)
+			err(1, "realloc()");
+		strlcpy(results[amount - 1].name, d->d_name, sizeof(results->name));
+	}
+	if (errno)
+		err(1, "readdir()");
+}
+
+void sortresults(const char *search)
+{
+	for (size_t i = 0; i < amount; ++i)
+		results[i].c = strsub(results[i].name, search);
+	qsort(results, amount, sizeof(struct result), colcmp);
+}
+
+void render(const char *buf, size_t bufi)
+{
+	sortresults(buf);
+	fprintf(stderr, "\r: %s\n", buf);
+	for (size_t i = 0; i < 5; ++i)
+		fprintf(stderr, "`%s'\n", results[i].name);
+	fprintf(stderr, "\033[6A\033[%dC", 2 + bufi);
+	fflush(stderr);
+}
+
+int main(int argc, char **argv)
+{
+	if (argc != 2 || !*argv[1])
+	{
+		fprintf(stderr, "usage: %s path\n", *argv);
+		return 1;
+	}
+
+	tcgetattr(0, &state);
+	for (int sig = 1; sig <= SIGRTMAX; ++sig)
+		signal(sig, getchhnd);
+	atexit(restore);
+
+	loadpath(argv[1]);
+
+	char buf[NAME_MAX+1] = {0};
+	size_t bufi = 0;
+
+	render("", 0);
+	for (int ch; (ch = getch());)
+	{
+		switch (ch)
+		{
+		case -1: case 4: case '\n': case '\r':
+			goto end;
+		case 127:
+		{
+			if (!bufi)
+				goto cont;
+			buf[--bufi] = 0;
+		} break;
+		default:
+		{
+			if (bufi == NAME_MAX)
+				goto cont;
+			buf[bufi++] = ch;
+		} break;
+		}
+		render(buf, bufi);
+cont:
+	}
+end:
+	fprintf(stderr, "\033[6B\rYou've chosen `", results[0].name);
+	fflush(stderr);
+	fprintf(stdout, "%s", results[0].name);
+	fflush(stdout);
+	fprintf(stderr, "'!\n");
+}