2235c7feac
MFC after: 2 weeks
459 lines
8.3 KiB
C
459 lines
8.3 KiB
C
/*
|
|
* Copyright (C) 1984-2021 Mark Nudelman
|
|
*
|
|
* You may distribute under the terms of either the GNU General Public
|
|
* License or the Less License, as specified in the README file.
|
|
*
|
|
* For more information, see the README file.
|
|
*/
|
|
|
|
|
|
#include "less.h"
|
|
#include "position.h"
|
|
|
|
extern IFILE curr_ifile;
|
|
extern int sc_height;
|
|
extern int jump_sline;
|
|
extern int perma_marks;
|
|
|
|
/*
|
|
* A mark is an ifile (input file) plus a position within the file.
|
|
*/
|
|
struct mark
|
|
{
|
|
/*
|
|
* Normally m_ifile != IFILE_NULL and m_filename == NULL.
|
|
* For restored marks we set m_filename instead of m_ifile
|
|
* because we don't want to create an ifile until the
|
|
* user explicitly requests the file (by name or mark).
|
|
*/
|
|
char m_letter; /* Associated character */
|
|
IFILE m_ifile; /* Input file being marked */
|
|
char *m_filename; /* Name of the input file */
|
|
struct scrpos m_scrpos; /* Position of the mark */
|
|
};
|
|
|
|
/*
|
|
* The table of marks.
|
|
* Each mark is identified by a lowercase or uppercase letter.
|
|
* The final one is lmark, for the "last mark"; addressed by the apostrophe.
|
|
*/
|
|
#define NMARKS ((2*26)+2) /* a-z, A-Z, mousemark, lastmark */
|
|
#define NUMARKS ((2*26)+1) /* user marks (not lastmark) */
|
|
#define MOUSEMARK (NMARKS-2)
|
|
#define LASTMARK (NMARKS-1)
|
|
static struct mark marks[NMARKS];
|
|
public int marks_modified = 0;
|
|
|
|
|
|
/*
|
|
* Initialize a mark struct.
|
|
*/
|
|
static void
|
|
cmark(m, ifile, pos, ln)
|
|
struct mark *m;
|
|
IFILE ifile;
|
|
POSITION pos;
|
|
int ln;
|
|
{
|
|
m->m_ifile = ifile;
|
|
m->m_scrpos.pos = pos;
|
|
m->m_scrpos.ln = ln;
|
|
m->m_filename = NULL;
|
|
}
|
|
|
|
/*
|
|
* Initialize the mark table to show no marks are set.
|
|
*/
|
|
public void
|
|
init_mark(VOID_PARAM)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < NMARKS; i++)
|
|
{
|
|
char letter;
|
|
switch (i) {
|
|
case MOUSEMARK: letter = '#'; break;
|
|
case LASTMARK: letter = '\''; break;
|
|
default: letter = (i < 26) ? 'a'+i : 'A'+i-26; break;
|
|
}
|
|
marks[i].m_letter = letter;
|
|
cmark(&marks[i], NULL_IFILE, NULL_POSITION, -1);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Set m_ifile and clear m_filename.
|
|
*/
|
|
static void
|
|
mark_set_ifile(m, ifile)
|
|
struct mark *m;
|
|
IFILE ifile;
|
|
{
|
|
m->m_ifile = ifile;
|
|
/* With m_ifile set, m_filename is no longer needed. */
|
|
free(m->m_filename);
|
|
m->m_filename = NULL;
|
|
}
|
|
|
|
/*
|
|
* Populate the m_ifile member of a mark struct from m_filename.
|
|
*/
|
|
static void
|
|
mark_get_ifile(m)
|
|
struct mark *m;
|
|
{
|
|
if (m->m_ifile != NULL_IFILE)
|
|
return; /* m_ifile is already set */
|
|
mark_set_ifile(m, get_ifile(m->m_filename, prev_ifile(NULL_IFILE)));
|
|
}
|
|
|
|
/*
|
|
* Return the user mark struct identified by a character.
|
|
*/
|
|
static struct mark *
|
|
getumark(c)
|
|
int c;
|
|
{
|
|
PARG parg;
|
|
if (c >= 'a' && c <= 'z')
|
|
return (&marks[c-'a']);
|
|
if (c >= 'A' && c <= 'Z')
|
|
return (&marks[c-'A'+26]);
|
|
if (c == '\'')
|
|
return (&marks[LASTMARK]);
|
|
if (c == '#')
|
|
return (&marks[MOUSEMARK]);
|
|
parg.p_char = (char) c;
|
|
error("Invalid mark letter %c", &parg);
|
|
return (NULL);
|
|
}
|
|
|
|
/*
|
|
* Get the mark structure identified by a character.
|
|
* The mark struct may either be in the mark table (user mark)
|
|
* or may be constructed on the fly for certain characters like ^, $.
|
|
*/
|
|
static struct mark *
|
|
getmark(c)
|
|
int c;
|
|
{
|
|
struct mark *m;
|
|
static struct mark sm;
|
|
|
|
switch (c)
|
|
{
|
|
case '^':
|
|
/*
|
|
* Beginning of the current file.
|
|
*/
|
|
m = &sm;
|
|
cmark(m, curr_ifile, ch_zero(), 0);
|
|
break;
|
|
case '$':
|
|
/*
|
|
* End of the current file.
|
|
*/
|
|
if (ch_end_seek())
|
|
{
|
|
error("Cannot seek to end of file", NULL_PARG);
|
|
return (NULL);
|
|
}
|
|
m = &sm;
|
|
cmark(m, curr_ifile, ch_tell(), sc_height);
|
|
break;
|
|
case '.':
|
|
/*
|
|
* Current position in the current file.
|
|
*/
|
|
m = &sm;
|
|
get_scrpos(&m->m_scrpos, TOP);
|
|
cmark(m, curr_ifile, m->m_scrpos.pos, m->m_scrpos.ln);
|
|
break;
|
|
case '\'':
|
|
/*
|
|
* The "last mark".
|
|
*/
|
|
m = &marks[LASTMARK];
|
|
break;
|
|
default:
|
|
/*
|
|
* Must be a user-defined mark.
|
|
*/
|
|
m = getumark(c);
|
|
if (m == NULL)
|
|
break;
|
|
if (m->m_scrpos.pos == NULL_POSITION)
|
|
{
|
|
error("Mark not set", NULL_PARG);
|
|
return (NULL);
|
|
}
|
|
break;
|
|
}
|
|
return (m);
|
|
}
|
|
|
|
/*
|
|
* Is a mark letter invalid?
|
|
*/
|
|
public int
|
|
badmark(c)
|
|
int c;
|
|
{
|
|
return (getmark(c) == NULL);
|
|
}
|
|
|
|
/*
|
|
* Set a user-defined mark.
|
|
*/
|
|
public void
|
|
setmark(c, where)
|
|
int c;
|
|
int where;
|
|
{
|
|
struct mark *m;
|
|
struct scrpos scrpos;
|
|
|
|
m = getumark(c);
|
|
if (m == NULL)
|
|
return;
|
|
get_scrpos(&scrpos, where);
|
|
if (scrpos.pos == NULL_POSITION)
|
|
{
|
|
bell();
|
|
return;
|
|
}
|
|
cmark(m, curr_ifile, scrpos.pos, scrpos.ln);
|
|
marks_modified = 1;
|
|
}
|
|
|
|
/*
|
|
* Clear a user-defined mark.
|
|
*/
|
|
public void
|
|
clrmark(c)
|
|
int c;
|
|
{
|
|
struct mark *m;
|
|
|
|
m = getumark(c);
|
|
if (m == NULL)
|
|
return;
|
|
if (m->m_scrpos.pos == NULL_POSITION)
|
|
{
|
|
bell();
|
|
return;
|
|
}
|
|
m->m_scrpos.pos = NULL_POSITION;
|
|
marks_modified = 1;
|
|
}
|
|
|
|
/*
|
|
* Set lmark (the mark named by the apostrophe).
|
|
*/
|
|
public void
|
|
lastmark(VOID_PARAM)
|
|
{
|
|
struct scrpos scrpos;
|
|
|
|
if (ch_getflags() & CH_HELPFILE)
|
|
return;
|
|
get_scrpos(&scrpos, TOP);
|
|
if (scrpos.pos == NULL_POSITION)
|
|
return;
|
|
cmark(&marks[LASTMARK], curr_ifile, scrpos.pos, scrpos.ln);
|
|
marks_modified = 1;
|
|
}
|
|
|
|
/*
|
|
* Go to a mark.
|
|
*/
|
|
public void
|
|
gomark(c)
|
|
int c;
|
|
{
|
|
struct mark *m;
|
|
struct scrpos scrpos;
|
|
|
|
m = getmark(c);
|
|
if (m == NULL)
|
|
return;
|
|
|
|
/*
|
|
* If we're trying to go to the lastmark and
|
|
* it has not been set to anything yet,
|
|
* set it to the beginning of the current file.
|
|
* {{ Couldn't we instead set marks[LASTMARK] in edit()? }}
|
|
*/
|
|
if (m == &marks[LASTMARK] && m->m_scrpos.pos == NULL_POSITION)
|
|
cmark(m, curr_ifile, ch_zero(), jump_sline);
|
|
|
|
mark_get_ifile(m);
|
|
|
|
/* Save scrpos; if it's LASTMARK it could change in edit_ifile. */
|
|
scrpos = m->m_scrpos;
|
|
if (m->m_ifile != curr_ifile)
|
|
{
|
|
/*
|
|
* Not in the current file; edit the correct file.
|
|
*/
|
|
if (edit_ifile(m->m_ifile))
|
|
return;
|
|
}
|
|
|
|
jump_loc(scrpos.pos, scrpos.ln);
|
|
}
|
|
|
|
/*
|
|
* Return the position associated with a given mark letter.
|
|
*
|
|
* We don't return which screen line the position
|
|
* is associated with, but this doesn't matter much,
|
|
* because it's always the first non-blank line on the screen.
|
|
*/
|
|
public POSITION
|
|
markpos(c)
|
|
int c;
|
|
{
|
|
struct mark *m;
|
|
|
|
m = getmark(c);
|
|
if (m == NULL)
|
|
return (NULL_POSITION);
|
|
|
|
if (m->m_ifile != curr_ifile)
|
|
{
|
|
error("Mark not in current file", NULL_PARG);
|
|
return (NULL_POSITION);
|
|
}
|
|
return (m->m_scrpos.pos);
|
|
}
|
|
|
|
/*
|
|
* Return the mark associated with a given position, if any.
|
|
*/
|
|
public char
|
|
posmark(pos)
|
|
POSITION pos;
|
|
{
|
|
int i;
|
|
|
|
/* Only user marks */
|
|
for (i = 0; i < NUMARKS; i++)
|
|
{
|
|
if (marks[i].m_ifile == curr_ifile && marks[i].m_scrpos.pos == pos)
|
|
{
|
|
if (i < 26) return 'a' + i;
|
|
if (i < 26*2) return 'A' + (i - 26);
|
|
return '#';
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Clear the marks associated with a specified ifile.
|
|
*/
|
|
public void
|
|
unmark(ifile)
|
|
IFILE ifile;
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < NMARKS; i++)
|
|
if (marks[i].m_ifile == ifile)
|
|
marks[i].m_scrpos.pos = NULL_POSITION;
|
|
}
|
|
|
|
/*
|
|
* Check if any marks refer to a specified ifile vi m_filename
|
|
* rather than m_ifile.
|
|
*/
|
|
public void
|
|
mark_check_ifile(ifile)
|
|
IFILE ifile;
|
|
{
|
|
int i;
|
|
char *filename = get_real_filename(ifile);
|
|
|
|
for (i = 0; i < NMARKS; i++)
|
|
{
|
|
struct mark *m = &marks[i];
|
|
char *mark_filename = m->m_filename;
|
|
if (mark_filename != NULL)
|
|
{
|
|
mark_filename = lrealpath(mark_filename);
|
|
if (strcmp(filename, mark_filename) == 0)
|
|
mark_set_ifile(m, ifile);
|
|
free(mark_filename);
|
|
}
|
|
}
|
|
}
|
|
|
|
#if CMD_HISTORY
|
|
|
|
/*
|
|
* Save marks to history file.
|
|
*/
|
|
public void
|
|
save_marks(fout, hdr)
|
|
FILE *fout;
|
|
char *hdr;
|
|
{
|
|
int i;
|
|
|
|
if (!perma_marks)
|
|
return;
|
|
|
|
fprintf(fout, "%s\n", hdr);
|
|
for (i = 0; i < NMARKS; i++)
|
|
{
|
|
char *filename;
|
|
struct mark *m = &marks[i];
|
|
char pos_str[INT_STRLEN_BOUND(m->m_scrpos.pos) + 2];
|
|
if (m->m_scrpos.pos == NULL_POSITION)
|
|
continue;
|
|
postoa(m->m_scrpos.pos, pos_str);
|
|
filename = m->m_filename;
|
|
if (filename == NULL)
|
|
filename = get_real_filename(m->m_ifile);
|
|
if (strcmp(filename, "-") != 0)
|
|
fprintf(fout, "m %c %d %s %s\n",
|
|
m->m_letter, m->m_scrpos.ln, pos_str, filename);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Restore one mark from the history file.
|
|
*/
|
|
public void
|
|
restore_mark(line)
|
|
char *line;
|
|
{
|
|
struct mark *m;
|
|
int ln;
|
|
POSITION pos;
|
|
|
|
#define skip_whitespace while (*line == ' ') line++
|
|
if (*line++ != 'm')
|
|
return;
|
|
skip_whitespace;
|
|
m = getumark(*line++);
|
|
if (m == NULL)
|
|
return;
|
|
skip_whitespace;
|
|
ln = lstrtoi(line, &line);
|
|
if (ln < 1)
|
|
ln = 1;
|
|
if (ln > sc_height)
|
|
ln = sc_height;
|
|
skip_whitespace;
|
|
pos = lstrtopos(line, &line);
|
|
skip_whitespace;
|
|
cmark(m, NULL_IFILE, pos, ln);
|
|
m->m_filename = save(line);
|
|
}
|
|
|
|
#endif /* CMD_HISTORY */
|