* add `#` at the beginning of the comand argument description to disable command when current window is a popup or the minibuf * update command definitions
361 lines
11 KiB
C
361 lines
11 KiB
C
/*
|
|
* LaTeX mode for QEmacs.
|
|
*
|
|
* Copyright (c) 2003 Martin Hedenfalk <mhe@home.se>
|
|
* Based on c-mode by Fabrice Bellard
|
|
* Requires the shell mode
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
* of this software and associated documentation files (the "Software"), to deal
|
|
* in the Software without restriction, including without limitation the rights
|
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
* copies of the Software, and to permit persons to whom the Software is
|
|
* furnished to do so, subject to the following conditions:
|
|
*
|
|
* The above copyright notice and this permission notice shall be included in
|
|
* all copies or substantial portions of the Software.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
|
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
* THE SOFTWARE.
|
|
*/
|
|
|
|
#include "qe.h"
|
|
|
|
enum {
|
|
TEX_TEX,
|
|
TEX_LATEX,
|
|
TEX_TEXINFO,
|
|
};
|
|
|
|
enum {
|
|
LATEX_STYLE_COMMENT = QE_STYLE_COMMENT,
|
|
LATEX_STYLE_STRING = QE_STYLE_STRING,
|
|
LATEX_STYLE_FUNCTION = QE_STYLE_FUNCTION,
|
|
LATEX_STYLE_KEYWORD = QE_STYLE_KEYWORD,
|
|
LATEX_STYLE_VARIABLE = QE_STYLE_VARIABLE,
|
|
};
|
|
|
|
/* TODO: add state handling to allow colorization of elements longer
|
|
* than one line (eg, multi-line functions and strings)
|
|
*/
|
|
static void latex_colorize_line(QEColorizeContext *cp,
|
|
const char32_t *str, int n,
|
|
QETermStyle *sbuf, ModeDef *syn)
|
|
{
|
|
int i = 0, start;
|
|
char32_t c;
|
|
int state = cp->colorize_state;
|
|
|
|
for (i = 0; i < n;) {
|
|
start = i;
|
|
c = str[i++];
|
|
switch (c) {
|
|
case '\0':
|
|
case '\n': /* Should not happen */
|
|
goto the_end;
|
|
case '`':
|
|
/* a ``string'' */
|
|
if (str[i] == '`') {
|
|
for (;;) {
|
|
i++;
|
|
if (str[i] == '\0') {
|
|
/* Should either flag an error or propagate
|
|
* string style to the next line
|
|
*/
|
|
break;
|
|
}
|
|
if (str[i] == '\'' && str[i + 1] == '\'') {
|
|
i += 2;
|
|
break;
|
|
}
|
|
}
|
|
SET_STYLE(sbuf, start, i, LATEX_STYLE_STRING);
|
|
}
|
|
break;
|
|
case '@':
|
|
if (syn->colorize_flags != TEX_TEXINFO)
|
|
break;
|
|
if (str[i] == 'c' && !qe_isalnum_(str[i + 1])) {
|
|
i = n;
|
|
SET_STYLE(sbuf, start, i, LATEX_STYLE_COMMENT);
|
|
break;
|
|
}
|
|
FALLTHROUGH;
|
|
|
|
case '\\':
|
|
/* \function[keyword]{variable} */
|
|
if (str[i] == '\'' || str[i] == '\"' || str[i] == '~'
|
|
|| str[i] == '%' || str[i] == '\\') {
|
|
i++;
|
|
} else {
|
|
while (str[i] != '\0' && str[i] != '{' && str[i] != '['
|
|
&& str[i] != ' ' && str[i] != '\\') {
|
|
i++;
|
|
}
|
|
}
|
|
SET_STYLE(sbuf, start, i, LATEX_STYLE_FUNCTION);
|
|
i = cp_skip_blanks(str, i, n);
|
|
while (str[i] == '{' || str[i] == '[') {
|
|
if (str[i++] == '[') {
|
|
/* handle [keyword] */
|
|
start = i;
|
|
while (str[i] != '\0' && str[i] != ']')
|
|
i++;
|
|
SET_STYLE(sbuf, start, i, LATEX_STYLE_KEYWORD);
|
|
if (str[i] == ']')
|
|
i++;
|
|
} else {
|
|
int braces = 0;
|
|
/* handle {variable} */
|
|
start = i;
|
|
while (str[i] != '\0') {
|
|
if (str[i] == '{') {
|
|
braces++;
|
|
} else
|
|
if (str[i] == '}') {
|
|
if (braces-- == 0)
|
|
break;
|
|
}
|
|
i++;
|
|
}
|
|
SET_STYLE(sbuf, start, i, LATEX_STYLE_VARIABLE);
|
|
if (str[i] == '}')
|
|
i++;
|
|
}
|
|
i = cp_skip_blanks(str, i, n);
|
|
}
|
|
break;
|
|
case '%':
|
|
if (syn->colorize_flags == TEX_TEXINFO)
|
|
break;
|
|
/* line comment */
|
|
i = n;
|
|
SET_STYLE(sbuf, start, i, LATEX_STYLE_COMMENT);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
the_end:
|
|
cp->colorize_state = state;
|
|
}
|
|
|
|
static int latex_mode_probe(ModeDef *mode, ModeProbeData *mp)
|
|
{
|
|
const u8 *p = mp->buf;
|
|
|
|
/* currently, only use the file extension */
|
|
/* Halibut (by Simon Tatham) has a syntax similar to TeX and uses
|
|
* .but extension */
|
|
if (match_extension(mp->filename, mode->extensions))
|
|
return 80;
|
|
|
|
/* Match TeX style sheets if they start with a comment */
|
|
if (match_extension(mp->filename, "sty|cls") && *p == '%')
|
|
return 80;
|
|
|
|
if (*p == '\\') {
|
|
/* match [\][a-z0-0_]+[{] */
|
|
while (qe_isalnum_(*++p))
|
|
continue;
|
|
if (*p == '{')
|
|
return 60;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
// XXX: With prefix argument, should always insert " characters.
|
|
static void do_tex_insert_quote(EditState *s)
|
|
{
|
|
EditBuffer *b = s->b;
|
|
int offset = s->offset;
|
|
char32_t c1 = eb_prevc(b, offset, &offset);
|
|
char32_t c2 = eb_prevc(b, offset, &offset);
|
|
|
|
// XXX: need more than 2 character backtrack
|
|
if (c1 == '\"') {
|
|
s->offset += eb_insert_char32(b, s->offset, '\"');
|
|
} else
|
|
if ((c1 == '`' || c1 == '\'') && c1 == c2) {
|
|
eb_delete_range(b, offset, s->offset);
|
|
s->offset += eb_insert_char32(b, s->offset, '\"');
|
|
} else {
|
|
if (c1 == '\n' || c1 == ' ') {
|
|
s->offset += eb_insert_str(b, s->offset, "``");
|
|
} else {
|
|
s->offset += eb_insert_str(b, s->offset, "''");
|
|
}
|
|
}
|
|
}
|
|
|
|
static struct latex_function {
|
|
const char *name;
|
|
const char *fmt;
|
|
int ask;
|
|
int output_to_buffer;
|
|
StringArray history;
|
|
EditState *es;
|
|
} latex_funcs[] = {
|
|
#define INIT_TAIL NULL_STRINGARRAY, NULL
|
|
{ "AmSTeX", "amstex '\\nonstopmode\\input %s'", 0, 1, INIT_TAIL },
|
|
{ "PDFLaTeX", "pdflatex '\\nonstopmode\\input{%s}'", 0, 1, INIT_TAIL },
|
|
{ "PDFTeX", "pdftex '\\nonstopmode\\input %s'", 0, 1, INIT_TAIL },
|
|
{ "Check", "lacheck %s", 0, 1, INIT_TAIL },
|
|
{ "BibTeX", "bibtex %s", 0, 1, INIT_TAIL },
|
|
{ "LaTeX", "latex --src-specials '\\nonstopmode\\input{%s}'", 0, 1, INIT_TAIL },
|
|
{ "ThumbPDF", "thumbpdf %s", 0, 1, INIT_TAIL },
|
|
{ "View", "xdvi %s.dvi -paper a4", 1, 0, INIT_TAIL },
|
|
{ "Print", "dvips %s -Plp", 1, 0, INIT_TAIL },
|
|
{ "File", "dvips %s.dvi -o %s.ps", 1, 1, INIT_TAIL },
|
|
{ NULL, NULL, 0, 0, INIT_TAIL },
|
|
#undef INIT_TAIL
|
|
};
|
|
|
|
static void latex_complete(CompleteState *cp, CompleteFunc enumerate) {
|
|
struct latex_function *func;
|
|
|
|
for (func = latex_funcs; func->name; func++) {
|
|
(*enumerate)(cp, func->name, CT_STRX);
|
|
}
|
|
}
|
|
|
|
static struct latex_function *find_latex_func(const char *name)
|
|
{
|
|
struct latex_function *func;
|
|
|
|
for (func = latex_funcs; func->name; func++) {
|
|
if (!strxcmp(func->name, name))
|
|
return func;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
static void latex_cmd_run(void *opaque, char *cmd,
|
|
qe__unused__ CompletionDef *completion)
|
|
{
|
|
struct latex_function *func = (struct latex_function *)opaque;
|
|
char dir[MAX_FILENAME_SIZE];
|
|
const char *path = NULL;
|
|
EditState *s = func->es;
|
|
QEmacsState *qs = s->qs;
|
|
|
|
if (cmd == NULL) {
|
|
put_error(s, "Aborted");
|
|
return;
|
|
}
|
|
|
|
/* get the directory of the open file and change into it */
|
|
path = get_default_path(s->b, s->b->total_size, dir, sizeof dir);
|
|
|
|
if (func->output_to_buffer) {
|
|
EditBuffer *b;
|
|
/* invoke command in shell buffer */
|
|
b = qe_new_shell_buffer(qs, NULL, s, "*LaTeX output*", NULL,
|
|
path, cmd, SF_COLOR |
|
|
SF_REUSE_BUFFER | SF_ERASE_BUFFER);
|
|
if (b) {
|
|
/* XXX: try to split window if necessary */
|
|
switch_to_buffer(s, b);
|
|
}
|
|
} else {
|
|
int pid = fork();
|
|
if (pid == 0) {
|
|
const char *argv[4];
|
|
|
|
if (path) chdir(path);
|
|
|
|
/* child process */
|
|
setsid();
|
|
|
|
argv[0] = get_shell();
|
|
argv[1] = "-c";
|
|
argv[2] = cmd;
|
|
argv[3] = NULL;
|
|
|
|
execv(argv[0], unconst(char * const*)argv);
|
|
exit(1);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void do_latex(EditState *e, const char *cmd)
|
|
{
|
|
char bname[MAX_FILENAME_SIZE];
|
|
char buf[1024];
|
|
struct latex_function *func;
|
|
|
|
/* strip extension from filename */
|
|
pstrcpy(bname, sizeof(bname), e->b->filename);
|
|
strip_extension(bname);
|
|
|
|
if (!cmd || cmd[0] == '\0')
|
|
cmd = "LaTeX";
|
|
|
|
/* check what command to run */
|
|
func = find_latex_func(cmd);
|
|
if (func) {
|
|
/* pass the EditState through to latex_cmd_run() */
|
|
func->es = e;
|
|
/* construct the command line to run */
|
|
strsubst(buf, sizeof(buf), func->fmt, "%s", bname);
|
|
if (func->ask) {
|
|
char prompt[128];
|
|
snprintf(prompt, sizeof(prompt), "%s command: ", func->name);
|
|
minibuffer_edit(e, buf, prompt, &func->history,
|
|
NULL /* completion */,
|
|
latex_cmd_run, func);
|
|
} else {
|
|
latex_cmd_run(func, buf, NULL);
|
|
}
|
|
} else {
|
|
put_error(e, "%s: No match", buf);
|
|
}
|
|
}
|
|
|
|
/* specific LaTeX commands */
|
|
static const CmdDef latex_commands[] = {
|
|
CMD2( "tex-insert-quote", "\"",
|
|
"Insert the appropriate quote marks for TeX",
|
|
do_tex_insert_quote, ES, "*")
|
|
CMD2( "TeX-command-master", "C-c C-c",
|
|
"Run the latex process",
|
|
do_latex, ESs,
|
|
"#" "s{Command: (default LaTeX) }[latex]|latex|")
|
|
};
|
|
|
|
static ModeDef latex_mode = {
|
|
.name = "LaTeX",
|
|
.extensions = "tex|but",
|
|
.mode_probe = latex_mode_probe,
|
|
.colorize_func = latex_colorize_line,
|
|
.colorize_flags = TEX_LATEX,
|
|
};
|
|
|
|
static ModeDef texinfo_mode = {
|
|
.name = "TeXinfo",
|
|
.extensions = "texi",
|
|
.colorize_func = latex_colorize_line,
|
|
.colorize_flags = TEX_TEXINFO,
|
|
};
|
|
|
|
static CompletionDef latex_completion = {
|
|
.name = "latex",
|
|
.enumerate = latex_complete,
|
|
};
|
|
|
|
static int latex_init(QEmacsState *qs)
|
|
{
|
|
qe_register_mode(qs, &latex_mode, MODEF_SYNTAX);
|
|
qe_register_mode(qs, &texinfo_mode, MODEF_SYNTAX);
|
|
qe_register_commands(qs, &latex_mode, latex_commands, countof(latex_commands));
|
|
qe_register_completion(qs, &latex_completion);
|
|
|
|
return 0;
|
|
}
|
|
|
|
qe_module_init(latex_init);
|