Coverage Report

Created: 2026-09-01 06:30

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/tmux/prompt-history.c
Line
Count
Source
1
/* $OpenBSD: prompt-history.c,v 1.2 2026/07/13 19:35:22 nicm Exp $ */
2
3
/*
4
 * Copyright (c) 2026 Nicholas Marriott <nicholas.marriott@gmail.com>
5
 *
6
 * Permission to use, copy, modify, and distribute this software for any
7
 * purpose with or without fee is hereby granted, provided that the above
8
 * copyright notice and this permission notice appear in all copies.
9
 *
10
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14
 * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
15
 * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
16
 * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17
 */
18
19
#include <sys/types.h>
20
21
#include <errno.h>
22
#include <stdlib.h>
23
#include <string.h>
24
25
#include "tmux.h"
26
27
static char *prompt_find_history_file(void);
28
static void  prompt_add_typed_history(char *);
29
30
/* Prompt history. */
31
static char **prompt_hlist[PROMPT_NTYPES];
32
static u_int    prompt_hsize[PROMPT_NTYPES];
33
34
/* Find the history file to load/save from/to. */
35
static char *
36
prompt_find_history_file(void)
37
0
{
38
0
  const char  *home, *history_file;
39
0
  char    *path;
40
41
0
  history_file = options_get_string(global_options, "history-file");
42
0
  if (*history_file == '\0')
43
0
    return (NULL);
44
0
  if (*history_file == '/')
45
0
    return (xstrdup(history_file));
46
47
0
  if (history_file[0] != '~' || history_file[1] != '/')
48
0
    return (NULL);
49
0
  if ((home = find_home()) == NULL)
50
0
    return (NULL);
51
0
  xasprintf(&path, "%s%s", home, history_file + 1);
52
0
  return (path);
53
0
}
54
55
/* Add loaded history item to the appropriate list. */
56
static void
57
prompt_add_typed_history(char *line)
58
0
{
59
0
  char      *typestr;
60
0
  enum prompt_type   type = PROMPT_TYPE_INVALID;
61
62
0
  typestr = strsep(&line, ":");
63
0
  if (line != NULL)
64
0
    type = prompt_type(typestr);
65
0
  if (type == PROMPT_TYPE_INVALID) {
66
    /*
67
     * Invalid types are not expected, but this provides backward
68
     * compatibility with old history files.
69
     */
70
0
    if (line != NULL)
71
0
      *(--line) = ':';
72
0
    prompt_add_history(typestr, PROMPT_TYPE_COMMAND);
73
0
  } else
74
0
    prompt_add_history(line, type);
75
0
}
76
77
/* Load prompt history from file. */
78
void
79
prompt_load_history(void)
80
0
{
81
0
  FILE  *f;
82
0
  char  *history_file, *line = NULL;
83
0
  size_t   length = 0;
84
0
  ssize_t  got;
85
86
0
  if ((history_file = prompt_find_history_file()) == NULL)
87
0
    return;
88
0
  log_debug("loading history from %s", history_file);
89
90
0
  f = fopen(history_file, "r");
91
0
  if (f == NULL) {
92
0
    log_debug("%s: %s", history_file, strerror(errno));
93
0
    free(history_file);
94
0
    return;
95
0
  }
96
0
  free(history_file);
97
98
0
  while ((got = getline(&line, &length, f)) != -1) {
99
0
    if (got > 0 && line[got - 1] == '\n')
100
0
      line[got - 1] = '\0';
101
0
    if (got > 0)
102
0
      prompt_add_typed_history(line);
103
0
  }
104
0
  free(line);
105
0
  fclose(f);
106
0
}
107
108
/* Save prompt history to file. */
109
void
110
prompt_save_history(void)
111
0
{
112
0
  FILE  *f;
113
0
  u_int  i, type;
114
0
  char  *history_file;
115
116
0
  if ((history_file = prompt_find_history_file()) == NULL)
117
0
    return;
118
0
  log_debug("saving history to %s", history_file);
119
120
0
  f = fopen(history_file, "w");
121
0
  if (f == NULL) {
122
0
    log_debug("%s: %s", history_file, strerror(errno));
123
0
    free(history_file);
124
0
    return;
125
0
  }
126
0
  free(history_file);
127
128
0
  for (type = 0; type < PROMPT_NTYPES; type++) {
129
0
    for (i = 0; i < prompt_hsize[type]; i++) {
130
0
      fputs(prompt_type_string(type), f);
131
0
      fputc(':', f);
132
0
      fputs(prompt_hlist[type][i], f);
133
0
      fputc('\n', f);
134
0
    }
135
0
  }
136
0
  fclose(f);
137
138
0
}
139
140
/* Get previous line from the history. */
141
const char *
142
prompt_up_history(u_int *idx, u_int type)
143
0
{
144
  /*
145
   * History runs from 0 to size - 1. Index is from 0 to size. Zero is
146
   * empty.
147
   */
148
149
0
  if (type >= PROMPT_NTYPES)
150
0
    return (NULL);
151
0
  if (prompt_hsize[type] == 0 || idx[type] == prompt_hsize[type])
152
0
    return (NULL);
153
0
  idx[type]++;
154
0
  return (prompt_hlist[type][prompt_hsize[type] - idx[type]]);
155
0
}
156
157
/* Get next line from the history. */
158
const char *
159
prompt_down_history(u_int *idx, u_int type)
160
0
{
161
0
  if (type >= PROMPT_NTYPES)
162
0
    return ("");
163
0
  if (prompt_hsize[type] == 0 || idx[type] == 0)
164
0
    return ("");
165
0
  idx[type]--;
166
0
  if (idx[type] == 0)
167
0
    return ("");
168
0
  return (prompt_hlist[type][prompt_hsize[type] - idx[type]]);
169
0
}
170
171
/* Add line to the history. */
172
void
173
prompt_add_history(const char *line, u_int type)
174
0
{
175
0
  u_int i, oldsize, newsize, freecount, hlimit, new = 1;
176
0
  size_t  movesize;
177
178
0
  if (type >= PROMPT_NTYPES)
179
0
    return;
180
181
0
  oldsize = prompt_hsize[type];
182
0
  if (oldsize > 0 &&
183
0
      strcmp(prompt_hlist[type][oldsize - 1], line) == 0)
184
0
    new = 0;
185
186
0
  hlimit = options_get_number(global_options, "prompt-history-limit");
187
0
  if (hlimit > oldsize) {
188
0
    if (new == 0)
189
0
      return;
190
0
    newsize = oldsize + new;
191
0
  } else {
192
0
    newsize = hlimit;
193
0
    freecount = oldsize + new - newsize;
194
0
    if (freecount > oldsize)
195
0
      freecount = oldsize;
196
0
    if (freecount == 0)
197
0
      return;
198
0
    for (i = 0; i < freecount; i++)
199
0
      free(prompt_hlist[type][i]);
200
0
    movesize = (oldsize - freecount) *
201
0
        sizeof *prompt_hlist[type];
202
0
    if (movesize > 0) {
203
0
      memmove(&prompt_hlist[type][0],
204
0
          &prompt_hlist[type][freecount], movesize);
205
0
    }
206
0
  }
207
208
0
  if (newsize == 0) {
209
0
    free(prompt_hlist[type]);
210
0
    prompt_hlist[type] = NULL;
211
0
  } else if (newsize != oldsize) {
212
0
    prompt_hlist[type] =
213
0
        xreallocarray(prompt_hlist[type], newsize,
214
0
      sizeof *prompt_hlist[type]);
215
0
  }
216
217
0
  if (new == 1 && newsize > 0)
218
0
    prompt_hlist[type][newsize - 1] = xstrdup(line);
219
0
  prompt_hsize[type] = newsize;
220
0
}
221
222
/* Get history size. */
223
u_int
224
prompt_history_size(enum prompt_type type)
225
0
{
226
0
  if (type >= PROMPT_NTYPES)
227
0
    return (0);
228
0
  return (prompt_hsize[type]);
229
0
}
230
231
/* Get history entry. */
232
const char *
233
prompt_history_get(enum prompt_type type, u_int idx)
234
0
{
235
0
  if (type >= PROMPT_NTYPES)
236
0
    return (NULL);
237
0
  if (idx >= prompt_hsize[type])
238
0
    return (NULL);
239
0
  return (prompt_hlist[type][idx]);
240
0
}
241
242
/* Clear prompt history. */
243
void
244
prompt_history_clear(enum prompt_type type)
245
0
{
246
0
  u_int idx;
247
248
0
  if (type >= PROMPT_NTYPES)
249
0
    return;
250
0
  for (idx = 0; idx < prompt_hsize[type]; idx++)
251
0
    free(prompt_hlist[type][idx]);
252
0
  free(prompt_hlist[type]);
253
  prompt_hlist[type] = NULL;
254
0
  prompt_hsize[type] = 0;
255
0
}