Files
flatpak/app/flatpak-complete.c
Alexander Larsson 3f4518b15c Run uncrustify
Closes: #1870
Approved by: alexlarsson
2018-07-08 10:05:37 +00:00

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);
}