diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/Makefile.am | 9 | ||||
-rw-r--r-- | src/help.cpp | 320 | ||||
-rw-r--r-- | src/help.h | 87 | ||||
-rw-r--r-- | src/parser.cpp | 2 |
4 files changed, 414 insertions, 4 deletions
diff --git a/src/Makefile.am b/src/Makefile.am index 515accd..314faa4 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -43,6 +43,7 @@ libsciteco_base_la_SOURCES = main.cpp sciteco.h \ spawn.cpp spawn.h \ glob.cpp glob.h \ goto.cpp goto.h \ + help.cpp help.h \ rbtree.cpp rbtree.h \ symbols.cpp symbols.h \ interface.cpp interface.h @@ -75,10 +76,10 @@ CLEANFILES = $(BUILT_SOURCES) \ symbols-scintilla.cpp : @SCINTILLA_PATH@/include/Scintilla.h \ symbols-extract.tes - $(BOOTSTRAP_SCITECO) -m @srcdir@/symbols-extract.tes \ - $< $@ "SCI_" scintilla + $(SCITECO_MINIMAL) -m @srcdir@/symbols-extract.tes \ + $< $@ "SCI_" scintilla symbols-scilexer.cpp : @SCINTILLA_PATH@/include/SciLexer.h \ symbols-extract.tes - $(BOOTSTRAP_SCITECO) -m @srcdir@/symbols-extract.tes \ - $< $@ "SCLEX_,SCE_" scilexer + $(SCITECO_MINIMAL) -m @srcdir@/symbols-extract.tes \ + $< $@ "SCLEX_,SCE_" scilexer diff --git a/src/help.cpp b/src/help.cpp new file mode 100644 index 0000000..9553f76 --- /dev/null +++ b/src/help.cpp @@ -0,0 +1,320 @@ +/* + * Copyright (C) 2012-2016 Robin Haberkorn + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <stdio.h> +#include <stdlib.h> +#include <errno.h> + +#include <glib.h> +#include <glib/gstdio.h> +#include <glib/gprintf.h> + +#include "sciteco.h" +#include "string-utils.h" +#include "parser.h" +#include "qregisters.h" +#include "ring.h" +#include "interface.h" +#include "help.h" + +namespace SciTECO { + +HelpIndex help_index; + +namespace States { + StateGetHelp gethelp; +} + +void +HelpIndex::load(void) +{ + gchar *lib_path; + gchar *women_path; + GDir *women_dir; + const gchar *basename; + + lib_path = QRegisters::globals["$SCITECOPATH"]->get_string(); + women_path = g_build_filename(lib_path, "women", NIL); + g_free(lib_path); + + women_dir = g_dir_open(women_path, 0, NULL); + if (!women_dir) { + g_free(women_path); + return; + } + + while ((basename = g_dir_read_name(women_dir))) { + gchar *filename, *filename_tec; + FILE *file; + gchar buffer[1024]; + gchar *topic; + + if (!g_str_has_suffix(basename, ".woman")) + continue; + + /* + * Open the corresponding SciTECO macro to read + * its first line. + */ + filename = g_build_filename(women_path, basename, NIL); + filename_tec = g_strconcat(filename, ".tec", NIL); + file = g_fopen(filename_tec, "r"); + g_free(filename_tec); + if (!file) { + /* + * There might simply be no support script for + * simple plain-text woman-pages. + * In this case we create a topic using the filename + * without an extension. + */ + topic = g_strndup(basename, strlen(basename)-6); + set(topic, filename); + g_free(topic); + g_free(filename); + continue; + } + + /* + * Each womanpage script begins with a special comment + * header containing the position to topic index. + * Every topic will be on its own line and they are unlikely + * to be very long, so we can use fgets() here. + * NOTE: Since we haven't opened with the "b" flag, + * fgets() will translate linebreaks to LF even on + * MSVCRT (Windows). + */ + if (!fgets(buffer, sizeof(buffer), file) || + !g_str_has_prefix(buffer, "!*")) { + interface.msg(InterfaceCurrent::MSG_WARNING, + "Missing or invalid topic line in womanpage script \"%s\"", + filename); + g_free(filename); + continue; + } + /* skip opening comment */ + topic = buffer+2; + + do { + gchar *endptr; + tecoInt pos = strtoul(topic, &endptr, 10); + gsize len; + + /* + * This also breaks at the last line of the + * header. + */ + if (*endptr != ':') + break; + + /* + * Strip the likely LF at the end of the line. + */ + len = strlen(endptr)-1; + if (G_LIKELY(endptr[len] == '\n')) + endptr[len] = '\0'; + + set(endptr+1, filename, pos); + } while ((topic = fgets(buffer, sizeof(buffer), file))); + + fclose(file); + g_free(filename); + } + + g_dir_close(women_dir); + g_free(women_path); +} + +HelpIndex::Topic * +HelpIndex::find(const gchar *name) +{ + Topic *ret; + + /* + * The topic index contains printable characters + * only (to avoid having to perform string building + * on the topic terms to be able to define control + * characters). + * Therefore, we expand control characters in the + * look-up string to their printable forms. + */ + gchar *term = String::canonicalize_ctl(name); + + Topic topic(term); + ret = (Topic *)RBTree::find(&topic); + g_free(term); + + return ret; +} + +void +HelpIndex::set(const gchar *name, const gchar *filename, tecoInt pos) +{ + Topic *topic = new Topic(name, filename, pos); + Topic *existing; + + existing = (Topic *)RBTree::find(topic); + if (existing) { + gchar *basename; + + if (!strcmp(existing->filename, filename)) { + /* + * A topic with the same name already exists + * in the same file. + * For the time being, we simply overwrite the + * last topic. + * FIXME: Perhaps make it unique again!? + */ + existing->pos = pos; + delete topic; + return; + } + + /* in another file -> make name unique */ + interface.msg(InterfaceCurrent::MSG_WARNING, + "Topic collision: \"%s\" defined in \"%s\" and \"%s\"", + name, existing->filename, filename); + + String::append(topic->name, ":"); + basename = g_path_get_basename(filename); + String::append(topic->name, basename); + g_free(basename); + } + + RBTree::insert(topic); +} + +/* + * Command states + */ + +/*$ + * ?[topic]$ -- Get help for topic + * + * Look up <topic> in the help index, opening + * the corresponding womanpage as a buffer and scrolling + * to the topic's position. + * The help index is built when this command is first + * executed, so the help system does not consume resources + * when not used (e.g. in a batch-mode script). + * + * \*(ST's help documents must be installed in the + * directory \fB$SCITECOPATH/women\fP, i.e. as part of + * the standard library. + * Each document consist of at least one plain-text file with + * the extension \(lq.woman\(rq. + * Optionally, a \*(ST script with the extension + * \(lq.woman.tec\(rq can be installed alongside the + * main document to define topics covered by this document + * and set up styling. + * + * The beginning of the script must be a header of the form: + * .EX + * !*\fIposition\fP:\fItopic1\fP + * \fIposition2\fP:\fItopic2\fP + * \fI...\fP + * *! + * .EE + * In other words it must be a \*(ST comment followed + * by an asterisk sign, followed by the first topic which + * is a buffer position, followed by a colon and the topic + * string. + * The topic string is terminated by the end of the line. + * The end of the header is marked by a single \(lq*!\(rq. + * Topic terms should be specified with printable characters + * only (e.g. use Caret+A instead of CTRL+A). + * When looking up a help term, control characters are + * canonicalized to their printable form, so the term + * \(lq^A\(rq is found both by Caret+A and CTRL+A. + * Also, while topic terms are not case folded, lookup + * is case insensitive. + * + * The rest of the script is not read by \*(ST internally + * but should contain styling for the main document. + * It is usually read by the standard library's lexer + * configuration system when showing a womanpage. + * If the \(lq.woman.tec\(rq macro is missing, + * \*(ST will define a single topic for the document based + * on the \(lq.woman\(rq file's name. + * + * The combination of plain-text document and script + * is called a \(lqwomanpage\(rq because these files + * are usually generated using \fBgroff\fP(1) with the + * \fIgrosciteco\fP formatter and the \fIsciteco.tmac\fP + * GNU troff macros. + * When using womanpages generated by \fIgrosciteco\fP, + * help topics can be defined using the \fBSCITECO_TOPIC\fP + * Troff macro. + * This flexible system allows \*(ST to access internal + * and third-party help files written in plain-text or + * with an arbitrary GNU troff macro package. + * As all GNU troff documents are processed at build-time, + * GNU troff is not required at runtime. + * + * The \fB?\fP command does not have string building enabled. + */ +void +StateGetHelp::initial(void) +{ + /* + * The help-index is populated on demand, + * so we start up quicker and batch mode does + * not depend on the availability of the standard + * library. + */ + if (G_UNLIKELY(!help_index.min())) + help_index.load(); +} + +State * +StateGetHelp::done(const gchar *str) +{ + HelpIndex::Topic *topic; + + BEGIN_EXEC(&States::start); + + topic = help_index.find(str); + if (!topic) + throw Error("Topic \"%s\" not found", str); + + ring.undo_edit(); + /* + * ED hooks with the default lexer framework + * will usually load the styling SciTECO script + * when editing the buffer for the first time. + */ + ring.edit(topic->filename); + + /* + * Make sure the topic is visible. + * We do need undo tokens for this (even though + * the buffer is removed on rubout if the woman + * page is viewed first) since we might browse + * multiple topics in the same buffer without + * closing it first. + */ + interface.undo_ssm(SCI_GOTOPOS, + interface.ssm(SCI_GETCURRENTPOS)); + interface.ssm(SCI_GOTOPOS, topic->pos); + + return &States::start; +} + +} /* namespace SciTECO */ diff --git a/src/help.h b/src/help.h new file mode 100644 index 0000000..db78979 --- /dev/null +++ b/src/help.h @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2012-2016 Robin Haberkorn + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef __HELP_H +#define __HELP_H + +#include <string.h> + +#include <glib.h> +#include <glib/gprintf.h> + +#include "sciteco.h" +#include "parser.h" +#include "undo.h" +#include "rbtree.h" + +namespace SciTECO { + +class HelpIndex : public RBTree { +public: + class Topic : public RBTree::RBEntry { + public: + gchar *name; + gchar *filename; + tecoInt pos; + + Topic(const gchar *_name, const gchar *_filename = NULL, tecoInt _pos = 0) + : name(g_strdup(_name)), + filename(_filename ? g_strdup(_filename) : NULL), + pos(_pos) {} + ~Topic() + { + g_free(name); + g_free(filename); + } + + int + operator <(RBEntry &l2) + { + return g_ascii_strcasecmp(name, ((Topic &)l2).name); + } + }; + + void load(void); + + Topic *find(const gchar *name); + + void set(const gchar *name, const gchar *filename, + tecoInt pos = 0); +}; + +extern HelpIndex help_index; + +/* + * Command states + */ + +class StateGetHelp : public StateExpectString { +public: + StateGetHelp() : StateExpectString(false) {} + +private: + void initial(void); + State *done(const gchar *str); +}; + +namespace States { + extern StateGetHelp gethelp; +} + +} /* namespace SciTECO */ + +#endif diff --git a/src/parser.cpp b/src/parser.cpp index 97822e7..00ebaee 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -40,6 +40,7 @@ #include "search.h" #include "spawn.h" #include "glob.h" +#include "help.h" #include "cmdline.h" #include "ioview.h" #include "error.h" @@ -687,6 +688,7 @@ StateStart::StateStart() : State() transitions['"'] = &States::condcommand; transitions['E'] = &States::ecommand; transitions['I'] = &States::insert_building; + transitions['?'] = &States::gethelp; transitions['S'] = &States::search; transitions['N'] = &States::searchall; |