mirror of
https://github.com/flatpak/flatpak.git
synced 2026-01-26 00:28:34 -05:00
529 lines
16 KiB
C
529 lines
16 KiB
C
/*
|
|
* Copyright © 2018 Red Hat, Inc
|
|
*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
* License as published by the Free Software Foundation; either
|
|
* version 2.1 of the License, or (at your option) any later version.
|
|
*
|
|
* This library 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
|
|
* Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
|
|
*
|
|
* Authors:
|
|
* Alexander Larsson <alexl@redhat.com>
|
|
*/
|
|
|
|
#include "config.h"
|
|
|
|
#include "flatpak-complete.h"
|
|
#include "flatpak-utils-private.h"
|
|
|
|
/* Uncomment to get debug traces in /tmp/flatpak-completion-debug.txt (nice
|
|
* to not have it interfere with stdout/stderr)
|
|
*/
|
|
#if 0
|
|
void
|
|
flatpak_completion_debug (const gchar *format, ...)
|
|
{
|
|
va_list var_args;
|
|
gchar *s;
|
|
static FILE *f = NULL;
|
|
|
|
va_start (var_args, format);
|
|
s = g_strdup_vprintf (format, var_args);
|
|
if (f == NULL)
|
|
f = fopen ("/tmp/flatpak-completion-debug.txt", "a+");
|
|
fprintf (f, "%s\n", s);
|
|
fflush (f);
|
|
g_free (s);
|
|
}
|
|
#else
|
|
void
|
|
flatpak_completion_debug (const gchar *format, ...)
|
|
{
|
|
}
|
|
#endif
|
|
|
|
static gboolean
|
|
is_word_separator (char c)
|
|
{
|
|
return g_ascii_isspace (c);
|
|
}
|
|
|
|
void
|
|
flatpak_complete_file (FlatpakCompletion *completion,
|
|
const char *file_type)
|
|
{
|
|
flatpak_completion_debug ("completing FILE");
|
|
g_print ("%s\n", file_type);
|
|
}
|
|
|
|
void
|
|
flatpak_complete_dir (FlatpakCompletion *completion)
|
|
{
|
|
flatpak_completion_debug ("completing DIR");
|
|
g_print ("%s\n", "__FLATPAK_DIR");
|
|
}
|
|
|
|
void
|
|
flatpak_complete_word (FlatpakCompletion *completion,
|
|
char *format, ...)
|
|
{
|
|
va_list args;
|
|
const char *rest;
|
|
const char *shell_cur;
|
|
const char *shell_cur_end;
|
|
g_autofree char *string = NULL;
|
|
|
|
g_return_if_fail (format != NULL);
|
|
|
|
#pragma GCC diagnostic push
|
|
#pragma GCC diagnostic ignored "-Wformat-nonliteral"
|
|
va_start (args, format);
|
|
string = g_strdup_vprintf (format, args);
|
|
va_end (args);
|
|
#pragma GCC diagnostic pop
|
|
|
|
if (!g_str_has_prefix (string, completion->cur))
|
|
return;
|
|
|
|
shell_cur = completion->shell_cur ? completion->shell_cur : "";
|
|
|
|
rest = string + strlen (completion->cur);
|
|
|
|
shell_cur_end = shell_cur + strlen (shell_cur);
|
|
while (shell_cur_end > shell_cur &&
|
|
rest > string &&
|
|
shell_cur_end[-1] == rest[-1] &&
|
|
/* I'm not sure exactly what bash is doing here with =, but this seems to work... */
|
|
shell_cur_end[-1] != '=')
|
|
{
|
|
rest--;
|
|
shell_cur_end--;
|
|
}
|
|
|
|
flatpak_completion_debug ("completing word: '%s' (%s)", string, rest);
|
|
|
|
g_print ("%s\n", rest);
|
|
}
|
|
|
|
void
|
|
flatpak_complete_ref (FlatpakCompletion *completion,
|
|
OstreeRepo *repo)
|
|
{
|
|
g_autoptr(GHashTable) refs = NULL;
|
|
flatpak_completion_debug ("completing REF");
|
|
|
|
if (ostree_repo_list_refs (repo,
|
|
NULL,
|
|
&refs, NULL, NULL))
|
|
{
|
|
GHashTableIter hashiter;
|
|
gpointer hashkey, hashvalue;
|
|
|
|
g_hash_table_iter_init (&hashiter, refs);
|
|
while ((g_hash_table_iter_next (&hashiter, &hashkey, &hashvalue)))
|
|
{
|
|
const char *ref = (const char *) hashkey;
|
|
if (!(g_str_has_prefix (ref, "runtime/") ||
|
|
g_str_has_prefix (ref, "app/")))
|
|
continue;
|
|
flatpak_complete_word (completion, "%s", ref);
|
|
}
|
|
}
|
|
}
|
|
|
|
static int
|
|
find_current_element (const char *str)
|
|
{
|
|
int count = 0;
|
|
|
|
if (g_str_has_prefix (str, "app/"))
|
|
str += strlen ("app/");
|
|
else if (g_str_has_prefix (str, "runtime/"))
|
|
str += strlen ("runtime/");
|
|
|
|
while (str != NULL && count <= 3)
|
|
{
|
|
str = strchr (str, '/');
|
|
count++;
|
|
if (str != NULL)
|
|
str = str + 1;
|
|
}
|
|
|
|
return count;
|
|
}
|
|
|
|
void
|
|
flatpak_complete_partial_ref (FlatpakCompletion *completion,
|
|
FlatpakKinds kinds,
|
|
const char *only_arch,
|
|
FlatpakDir *dir,
|
|
const char *remote)
|
|
{
|
|
FlatpakKinds matched_kinds;
|
|
const char *pref;
|
|
g_autofree char *id = NULL;
|
|
g_autofree char *arch = NULL;
|
|
g_autofree char *branch = NULL;
|
|
|
|
g_auto(GStrv) refs = NULL;
|
|
int element;
|
|
const char *cur_parts[4] = { NULL };
|
|
g_autoptr(GError) error = NULL;
|
|
int i;
|
|
|
|
pref = completion->cur;
|
|
element = find_current_element (pref);
|
|
|
|
flatpak_split_partial_ref_arg_novalidate (pref, kinds,
|
|
NULL, NULL,
|
|
&matched_kinds, &id, &arch, &branch);
|
|
|
|
cur_parts[1] = id;
|
|
cur_parts[2] = arch ? arch : "";
|
|
cur_parts[3] = branch ? branch : "";
|
|
|
|
if (remote)
|
|
{
|
|
refs = flatpak_dir_find_remote_refs (dir, completion->argv[1],
|
|
(element > 1) ? id : NULL,
|
|
(element > 3) ? branch : NULL,
|
|
(element > 2) ? arch : only_arch,
|
|
matched_kinds, NULL, &error);
|
|
}
|
|
else
|
|
{
|
|
refs = flatpak_dir_find_installed_refs (dir,
|
|
(element > 1) ? id : NULL,
|
|
(element > 3) ? branch : NULL,
|
|
(element > 2) ? arch : only_arch,
|
|
matched_kinds, &error);
|
|
}
|
|
if (refs == NULL)
|
|
flatpak_completion_debug ("find refs error: %s", error->message);
|
|
for (i = 0; refs != NULL && refs[i] != NULL; i++)
|
|
{
|
|
int j;
|
|
g_autoptr(GString) comp = NULL;
|
|
g_auto(GStrv) parts = flatpak_decompose_ref (refs[i], NULL);
|
|
if (parts == NULL)
|
|
continue;
|
|
|
|
if (!g_str_has_prefix (parts[element], cur_parts[element]))
|
|
continue;
|
|
|
|
if (flatpak_id_has_subref_suffix (parts[element]))
|
|
{
|
|
char *last_dot = strrchr (parts[element], '.');
|
|
|
|
if (last_dot == NULL)
|
|
continue; /* Shouldn't really happen */
|
|
|
|
/* Only complete to subrefs is fully matching real part.
|
|
* For example, only match org.foo.Bar.Sources for
|
|
* "org.foo.Bar", "org.foo.Bar." or "org.foo.Bar.S", but
|
|
* not for "org.foo" or other shorter prefixes.
|
|
*/
|
|
if (strncmp (parts[element], cur_parts[element], last_dot - parts[element] - 1) != 0)
|
|
continue;
|
|
}
|
|
|
|
comp = g_string_new (pref);
|
|
g_string_append (comp, parts[element] + strlen (cur_parts[element]));
|
|
|
|
/* Only complete on the last part if the user explicitly adds a / */
|
|
if (element >= 2)
|
|
{
|
|
for (j = element + 1; j < 4; j++)
|
|
{
|
|
g_string_append (comp, "/");
|
|
g_string_append (comp, parts[j]);
|
|
}
|
|
}
|
|
|
|
flatpak_complete_word (completion, "%s", comp->str);
|
|
}
|
|
}
|
|
|
|
static gboolean
|
|
switch_already_in_line (FlatpakCompletion *completion,
|
|
GOptionEntry *entry)
|
|
{
|
|
guint i = 0;
|
|
guint line_part_len = 0;
|
|
|
|
for (; i < completion->original_argc; ++i)
|
|
{
|
|
line_part_len = strlen (completion->original_argv[i]);
|
|
if (line_part_len > 2 &&
|
|
g_strcmp0 (&completion->original_argv[i][2], entry->long_name) == 0)
|
|
return TRUE;
|
|
|
|
if (line_part_len == 2 &&
|
|
completion->original_argv[i][1] == entry->short_name)
|
|
return TRUE;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
static gboolean
|
|
should_filter_out_option_from_completion (FlatpakCompletion *completion,
|
|
GOptionEntry *entry)
|
|
{
|
|
switch (entry->arg)
|
|
{
|
|
case G_OPTION_ARG_NONE:
|
|
case G_OPTION_ARG_STRING:
|
|
case G_OPTION_ARG_INT:
|
|
case G_OPTION_ARG_FILENAME:
|
|
case G_OPTION_ARG_DOUBLE:
|
|
case G_OPTION_ARG_INT64:
|
|
return switch_already_in_line (completion, entry);
|
|
|
|
default:
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
void
|
|
flatpak_complete_options (FlatpakCompletion *completion,
|
|
GOptionEntry *entries)
|
|
{
|
|
GOptionEntry *e = entries;
|
|
int i;
|
|
|
|
while (e->long_name != NULL)
|
|
{
|
|
if (e->arg_description)
|
|
{
|
|
g_autofree char *prefix = g_strdup_printf ("--%s=", e->long_name);
|
|
|
|
if (g_str_has_prefix (completion->cur, prefix))
|
|
{
|
|
if (strcmp (e->arg_description, "ARCH") == 0)
|
|
{
|
|
const char *arches[] = {"i386", "x86_64", "aarch64", "arm"};
|
|
for (i = 0; i < G_N_ELEMENTS (arches); i++)
|
|
flatpak_complete_word (completion, "%s%s ", prefix, arches[i]);
|
|
}
|
|
else if (strcmp (e->arg_description, "SHARE") == 0)
|
|
{
|
|
for (i = 0; flatpak_context_shares[i] != NULL; i++)
|
|
flatpak_complete_word (completion, "%s%s ", prefix, flatpak_context_shares[i]);
|
|
}
|
|
else if (strcmp (e->arg_description, "DEVICE") == 0)
|
|
{
|
|
for (i = 0; flatpak_context_devices[i] != NULL; i++)
|
|
flatpak_complete_word (completion, "%s%s ", prefix, flatpak_context_devices[i]);
|
|
}
|
|
else if (strcmp (e->arg_description, "FEATURE") == 0)
|
|
{
|
|
for (i = 0; flatpak_context_features[i] != NULL; i++)
|
|
flatpak_complete_word (completion, "%s%s ", prefix, flatpak_context_features[i]);
|
|
}
|
|
else if (strcmp (e->arg_description, "SOCKET") == 0)
|
|
{
|
|
for (i = 0; flatpak_context_sockets[i] != NULL; i++)
|
|
flatpak_complete_word (completion, "%s%s ", prefix, flatpak_context_sockets[i]);
|
|
}
|
|
else if (strcmp (e->arg_description, "FILE") == 0)
|
|
{
|
|
flatpak_complete_file (completion, "__FLATPAK_FILE");
|
|
}
|
|
else
|
|
flatpak_complete_word (completion, "%s", prefix);
|
|
}
|
|
else
|
|
flatpak_complete_word (completion, "%s", prefix);
|
|
}
|
|
else
|
|
{
|
|
/* If this is just a switch, then don't add it multiple
|
|
* times */
|
|
if (!should_filter_out_option_from_completion (completion, e))
|
|
{
|
|
flatpak_complete_word (completion, "--%s ", e->long_name);
|
|
}
|
|
else
|
|
{
|
|
flatpak_completion_debug ("switch --%s is already in line %s", e->long_name, completion->line);
|
|
}
|
|
}
|
|
|
|
/* We may end up checking switch_already_in_line twice, but this is
|
|
* for simplicity's sake - the alternative solution would be to
|
|
* continue the loop early and have to increment e. */
|
|
if (e->short_name != 0)
|
|
{
|
|
/* This is a switch, we may not want to add it */
|
|
if (!e->arg_description)
|
|
{
|
|
if (!should_filter_out_option_from_completion (completion, e))
|
|
{
|
|
flatpak_complete_word (completion, "-%c ", e->short_name);
|
|
}
|
|
else
|
|
{
|
|
flatpak_completion_debug ("switch -%c is already in line %s", e->short_name, completion->line);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
flatpak_complete_word (completion, "-%c ", e->short_name);
|
|
}
|
|
}
|
|
e++;
|
|
}
|
|
}
|
|
|
|
void
|
|
flatpak_complete_context (FlatpakCompletion *completion)
|
|
{
|
|
flatpak_complete_options (completion, flatpak_context_get_option_entries ());
|
|
}
|
|
|
|
static gchar *
|
|
pick_word_at (const char *s,
|
|
int cursor,
|
|
int *out_word_begins_at)
|
|
{
|
|
int begin, end;
|
|
|
|
if (s[0] == '\0')
|
|
{
|
|
if (out_word_begins_at != NULL)
|
|
*out_word_begins_at = -1;
|
|
return NULL;
|
|
}
|
|
|
|
if (is_word_separator (s[cursor]) && ((cursor > 0 && is_word_separator (s[cursor - 1])) || cursor == 0))
|
|
{
|
|
if (out_word_begins_at != NULL)
|
|
*out_word_begins_at = cursor;
|
|
return g_strdup ("");
|
|
}
|
|
|
|
while (!is_word_separator (s[cursor - 1]) && cursor > 0)
|
|
cursor--;
|
|
begin = cursor;
|
|
|
|
end = begin;
|
|
while (!is_word_separator (s[end]) && s[end] != '\0')
|
|
end++;
|
|
|
|
if (out_word_begins_at != NULL)
|
|
*out_word_begins_at = begin;
|
|
|
|
return g_strndup (s + begin, end - begin);
|
|
}
|
|
|
|
static gboolean
|
|
parse_completion_line_to_argv (const char *initial_completion_line,
|
|
FlatpakCompletion *completion)
|
|
{
|
|
gboolean parse_result = g_shell_parse_argv (initial_completion_line,
|
|
&completion->original_argc,
|
|
&completion->original_argv,
|
|
NULL);
|
|
|
|
/* Make a shallow copy of argv, which will be our "working set" */
|
|
completion->argc = completion->original_argc;
|
|
completion->argv = g_memdup (completion->original_argv,
|
|
sizeof (gchar *) * (completion->original_argc + 1));
|
|
|
|
return parse_result;
|
|
}
|
|
|
|
FlatpakCompletion *
|
|
flatpak_completion_new (const char *arg_line,
|
|
const char *arg_point,
|
|
const char *arg_cur)
|
|
{
|
|
FlatpakCompletion *completion;
|
|
g_autofree char *initial_completion_line = NULL;
|
|
int _point;
|
|
char *endp;
|
|
int cur_begin;
|
|
int i;
|
|
|
|
_point = strtol (arg_point, &endp, 10);
|
|
if (endp == arg_point || *endp != '\0')
|
|
return NULL;
|
|
|
|
completion = g_new0 (FlatpakCompletion, 1);
|
|
completion->line = g_strdup (arg_line);
|
|
completion->shell_cur = g_strdup (arg_cur);
|
|
completion->point = _point;
|
|
|
|
flatpak_completion_debug ("========================================");
|
|
flatpak_completion_debug ("completion_point=%d", completion->point);
|
|
flatpak_completion_debug ("completion_shell_cur='%s'", completion->shell_cur);
|
|
flatpak_completion_debug ("----");
|
|
flatpak_completion_debug (" 0123456789012345678901234567890123456789012345678901234567890123456789");
|
|
flatpak_completion_debug ("'%s'", completion->line);
|
|
flatpak_completion_debug (" %*s^", completion->point, "");
|
|
|
|
/* compute cur and prev */
|
|
completion->prev = NULL;
|
|
completion->cur = pick_word_at (completion->line, completion->point, &cur_begin);
|
|
if (cur_begin > 0)
|
|
{
|
|
gint prev_end;
|
|
for (prev_end = cur_begin - 1; prev_end >= 0; prev_end--)
|
|
{
|
|
if (!is_word_separator (completion->line[prev_end]))
|
|
{
|
|
completion->prev = pick_word_at (completion->line, prev_end, NULL);
|
|
break;
|
|
}
|
|
}
|
|
|
|
initial_completion_line = g_strndup (completion->line, cur_begin);
|
|
}
|
|
else
|
|
initial_completion_line = g_strdup ("");
|
|
|
|
flatpak_completion_debug ("'%s'", initial_completion_line);
|
|
flatpak_completion_debug ("----");
|
|
|
|
flatpak_completion_debug (" cur='%s'", completion->cur);
|
|
flatpak_completion_debug ("prev='%s'", completion->prev);
|
|
|
|
if (!parse_completion_line_to_argv (initial_completion_line,
|
|
completion))
|
|
{
|
|
/* it's very possible the command line can't be parsed (for
|
|
* example, missing quotes etc) - in that case, we just
|
|
* don't autocomplete at all
|
|
*/
|
|
flatpak_completion_free (completion);
|
|
return NULL;
|
|
}
|
|
|
|
flatpak_completion_debug ("completion_argv %i:", completion->original_argc);
|
|
for (i = 0; i < completion->original_argc; i++)
|
|
flatpak_completion_debug (completion->original_argv[i]);
|
|
|
|
flatpak_completion_debug ("----");
|
|
|
|
return completion;
|
|
}
|
|
|
|
void
|
|
flatpak_completion_free (FlatpakCompletion *completion)
|
|
{
|
|
g_free (completion->cur);
|
|
g_free (completion->prev);
|
|
g_free (completion->line);
|
|
g_free (completion->argv);
|
|
g_strfreev (completion->original_argv);
|
|
g_free (completion);
|
|
}
|