1 #include "help.h"
2 #include "texdocdialog.h"
3 #include "smallUsefulFunctions.h"
4 #include "utilsSystem.h"
5 #include "configmanager.h"
6 #include <QProcessEnvironment>
7 #include <QMutex>
8
Help(QObject * parent)9 Help::Help(QObject *parent): QObject(parent),texDocSystem(0)
10 {
11
12 }
13
14 /*!
15 * \brief execute a dialog to let the user choose a package to show its documentation
16 * \param packages
17 * \param defaultPackage
18 */
execTexdocDialog(const QStringList & packages,const QString & defaultPackage)19 void Help::execTexdocDialog(const QStringList &packages, const QString &defaultPackage)
20 {
21 TexdocDialog dialog(nullptr,this);
22 dialog.setPackageNames(packages);
23 if (!defaultPackage.isEmpty()) {
24 dialog.setPreferredPackage(defaultPackage);
25 }
26 if (dialog.exec()) {
27 viewTexdoc(dialog.selectedPackage());
28 }
29 }
30 /*!
31 * \brief run texdoc --view package
32 * \param package
33 */
viewTexdoc(QString package)34 void Help::viewTexdoc(QString package)
35 {
36 if (package.isEmpty()) {
37 QAction *act = qobject_cast<QAction *>(sender());
38 if (!act) return;
39 package = act->data().toString();
40 }
41 if (!package.isEmpty()) {
42 QString answer=runTexdoc("--view "+package);
43 }
44 }
45
46 /*!
47 * \brief check if system runs miktex
48 * Tries to run texdoc --veriosn and analyzes result.
49 * Miktex starts with MikTeX ...
50 * \return
51 */
isMiktexTexdoc()52 bool Help::isMiktexTexdoc()
53 {
54 if (!texDocSystem) {
55 QString answer=runTexdoc("--version");
56 texDocSystem = answer.startsWith("MiKTeX") ? 1 : 2;
57 }
58 return (texDocSystem == 1);
59 }
60
isTexdocExpectedToFinish()61 bool Help::isTexdocExpectedToFinish()
62 {
63 if (!isMiktexTexdoc()) return true;
64 QProcessEnvironment env = QProcessEnvironment::systemEnvironment();
65 foreach (const QString &var, env.keys()) {
66 if (var.startsWith("MIKTEX_VIEW_")) {
67 // miktex texdoc will run as long as the viewer is opened when the MIKTEX_VIEW_* variables are set
68 // http://docs.miktex.org/manual/mthelp.html
69 return false;
70 }
71 }
72 return true;
73 }
74
75 /*!
76 * \brief search for documentation files for a given package
77 * It uses texdoc to access that information.
78 * \param package
79 * \param silent
80 * \return
81 */
packageDocFile(const QString & package,bool silent)82 QString Help::packageDocFile(const QString &package, bool silent)
83 {
84 QString cmd = BuildManager::CMD_TEXDOC;
85 if (cmd.isEmpty()) {
86 if (!silent) UtilsUi::txsWarning(tr("texdoc not found."));
87 return QString();
88 }
89 QStringList args;
90 if (isMiktexTexdoc()) {
91 args << "--list-only";
92 } else {
93 args << "--list" << "--machine";
94 }
95 args << package;
96
97
98 QString output =runTexdoc(args.join(" "));
99
100 QStringList allFiles;
101 if (isMiktexTexdoc()) {
102 allFiles = output.split("\r\n");
103 } else {
104 foreach (const QString &line, output.split("\n")) {
105 QStringList cols = line.simplified().split(" ");
106 if (cols.count() > 2)
107 allFiles << cols.at(2);
108 }
109 }
110 foreach (const QString &file, allFiles) {
111 if (file.endsWith(".pdf") || file.endsWith(".dvi"))
112 return file;
113 }
114 return QString();
115 }
116 /*!
117 * \brief search for documentation files for a given package asynchrnously
118 * It uses texdoc to access that information.
119 * The results are processed in texdocAvailableRequestFinished
120 * \param package
121 * \param silent
122 * \return
123 */
texdocAvailableRequest(const QString & package)124 void Help::texdocAvailableRequest(const QString &package)
125 {
126 if (package.isEmpty())
127 return;
128 if (BuildManager::CMD_TEXDOC.isEmpty()) {
129 emit texdocAvailableReply(package, false, tr("texdoc not found."));
130 return;
131 }
132
133 QStringList args;
134 if (isMiktexTexdoc()) {
135 args << "--print-only" << package;
136 } else {
137 args << "--list" << "--machine" << package; // --print-only does not exist in texlive 2012, actual is response is not used either ...
138 // TODO: not the right option: don't open the viewer here
139 // There seems to be no option yielding only the would be called command
140 // Alternative: texdoc --list -M and parse the first line for the package name
141 }
142 runTexdocAsync(args.join(" "),SLOT(texdocAvailableRequestFinished(int,QProcess::ExitStatus)));
143
144 }
texdocAvailableRequestFinished(int,QProcess::ExitStatus status)145 void Help::texdocAvailableRequestFinished(int,QProcess::ExitStatus status){
146
147 if(status!=QProcess::NormalExit) return; // texdoc --list failed
148
149 ProcessX *proc=qobject_cast<ProcessX *>(sender());
150 QString *buffer=proc->getStdoutBuffer();
151 QString cmdLine=proc->getCommandLine();
152 int i=cmdLine.lastIndexOf(" ");
153 QString package;
154 if(i>-1){
155 package=cmdLine.mid(i+1);
156 }
157
158
159 if(buffer==nullptr) return; // sanity check
160
161 if(!isMiktexTexdoc() && !buffer->isEmpty()){
162 // analyze texdoc --list result in more detail, as it gives results even for partially matched names
163 QStringList lines=buffer->split("\n");
164 QString line=lines.first();
165 QStringList cols=line.split("\t");
166 if(cols.count()>4){
167 if(cols.value(1).startsWith("-")){
168 buffer->clear(); // only partial, no real match
169 }
170 }
171 }
172
173 emit texdocAvailableReply(package, !buffer->isEmpty(), QString());
174
175 delete buffer;
176 }
177
178 /*!
179 * \brief run texdoc command
180 * \param args
181 * \return
182 */
runTexdoc(QString args)183 QString Help::runTexdoc(QString args)
184 {
185 QString output;
186 emit statusMessage(QString(" texdoc "));
187 emit runCommand(BuildManager::CMD_TEXDOC+" "+args, &output);
188 return output;
189 }
190 /*!
191 * \brief run texdoc command asynchronously
192 * \param args
193 * \param finishedCMD SLOT for return path
194 * \return
195 */
runTexdocAsync(QString args,const char * finishedCMD)196 bool Help::runTexdocAsync(QString args,const char * finishedCMD)
197 {
198 emit statusMessage(QString(" texdoc (async)"));
199 emit runCommandAsync(BuildManager::CMD_TEXDOC+" "+args, finishedCMD);
200 return true;
201 }
202
203
204
205
LatexReference(QObject * parent)206 LatexReference::LatexReference(QObject *parent) : QObject(parent) {}
207
setFile(QString filename)208 void LatexReference::setFile(QString filename)
209 {
210 m_filename = filename;
211 if (filename.isEmpty()) return;
212
213 QFile f(filename);
214 if (!f.open(QFile::ReadOnly | QFile::Text)) return;
215 QTextStream stream(&f);
216 #if QT_VERSION < QT_VERSION_CHECK(6,0,0)
217 stream.setCodec("UTF-8");
218 #endif
219 m_htmltext = stream.readAll();
220 makeIndex();
221 }
222
contains(const QString & command)223 bool LatexReference::contains(const QString &command)
224 {
225 return m_anchors.contains(command);
226 }
227
228 /// tries to generate a text of suitable length for display as a tooltip
getTextForTooltip(const QString & command)229 QString LatexReference::getTextForTooltip(const QString &command)
230 {
231 QString sectionText = getSectionText(command);
232 QString partialText;
233 if (sectionText.count('\n') > 30) { // tooltip would be very large: try to get a reasonable smaller string
234 if (command.startsWith("\\begin{")) {
235 return truncateLines(sectionText, 30);
236 } else {
237 partialText = getPartialText(command);
238 int nr=partialText.count('\n');
239 if(nr<10)
240 return truncateLines(sectionText, 30);
241 if (!partialText.isEmpty()) return partialText;
242 }
243 }
244 return sectionText;
245 }
246
247 /*! get all the text in the section describing the command
248 * it starts with the first heading after the section anchor and ranges down to the next <hr>
249 */
getSectionText(const QString & command)250 QString LatexReference::getSectionText(const QString &command)
251 {
252 Anchor sAnchor(m_sectionAnchors[command]);
253 if (sAnchor.name.isNull()) return QString();
254 if (sAnchor.start_pos < 0) {
255 sAnchor.start_pos = m_htmltext.indexOf(QString("<a name=\"%1\">").arg(sAnchor.name));
256 sAnchor.start_pos = m_htmltext.indexOf("<h", sAnchor.start_pos); // skip header div by going to next headline
257 }
258 if (sAnchor.start_pos < 0) return QString();
259 if (sAnchor.end_pos < 0) {
260 QString endTag("<hr>");
261 sAnchor.end_pos = m_htmltext.indexOf(endTag, sAnchor.start_pos);
262 m_sectionAnchors.insert(command, sAnchor); // save positions for a faster lookup next time
263 }
264 return m_htmltext.mid(sAnchor.start_pos, sAnchor.end_pos - sAnchor.start_pos);
265 }
266
267 /*! get only a partial description for the command
268 * the serach looks for the following block types (sqare brackets mark the extrated text:
269 * [<dt>(anchor-in-here)</dt><dd></dd>]
270 * </div>[(anchor-in-here)]</a name=
271 */
getPartialText(const QString & command)272 QString LatexReference::getPartialText(const QString &command)
273 {
274 static QRegularExpression startTag("<(dt|/div)>");
275 QString endTag;
276 int endOffset = 0;
277
278 Anchor anchor(m_anchors[command]);
279 if (anchor.name.isNull()) return QString();
280 if (anchor.start_pos < 0) {
281 anchor.start_pos = m_htmltext.indexOf(QString("<a name=\"%1\">").arg(anchor.name));
282 QRegularExpressionMatch match;
283 anchor.start_pos = m_htmltext.lastIndexOf(startTag, anchor.start_pos,&match);
284 if (match.captured(1) == "dt") {
285 endTag = "</dd>";
286 endOffset = endTag.length();
287 } else {
288 anchor.start_pos += QString("</div>").length();
289 endTag = "</p>";
290 }
291 }
292 if (anchor.start_pos < 0) return QString();
293 if (anchor.end_pos < 0) {
294 anchor.end_pos = m_htmltext.indexOf(endTag, anchor.start_pos + 1);
295 if (anchor.end_pos >= 0) anchor.end_pos += endOffset;
296 int hrEnd = m_htmltext.indexOf("<hr>", anchor.start_pos + 1);
297 if (hrEnd >= 0 && hrEnd < anchor.end_pos) // don't go further than the next <hr>
298 anchor.end_pos = hrEnd;
299 m_anchors.insert(command, anchor); // save positions for a faster lookup next time
300 }
301 return m_htmltext.mid(anchor.start_pos, anchor.end_pos - anchor.start_pos);
302 }
303
304 /* parses the index of the reference manual and extracts the anchor names for the commands */
makeIndex()305 void LatexReference::makeIndex()
306 {
307 QString startTag("<table class=\"index-cp\"");
308 QString endTag("</table>");
309
310 int start = m_htmltext.indexOf(startTag);
311 if (start < 0) return;
312 int end = m_htmltext.indexOf(endTag, start);
313 int length = end < 0 ? -1 : end - start + endTag.length();
314 QString indexText = m_htmltext.mid(start, length);
315
316 QRegularExpression rx("<a href=\"#([^\"]+)\"><code>([^\\s<]+)[^\n]*<a href=\"#([^\"]+)\">([^<]+)</a>");
317 int pos = 0;
318 while (pos >= 0) {
319 QRegularExpressionMatch match;
320 pos = indexText.indexOf(rx, pos,&match);
321 if (pos < 0) break;
322 QString anchorName(match.captured(1));
323 QString word(match.captured(2));
324 QString sectionAnchorName(match.captured(3));
325 //QString sectionTitle(match.captured(4));
326 if (word.startsWith('\\')) { // a command
327 if (word == "\\begin" || word == "\\") {
328 // don't add these words to the index because they give a mess in the tooltips
329 pos += match.capturedLength();
330 continue;
331 }
332 m_anchors.insert(word, Anchor(anchorName));
333 m_sectionAnchors.insert(word, Anchor(sectionAnchorName));
334 } else if (anchorName.contains("environment")) { // an environment
335 m_anchors.insert("\\begin{" + word, Anchor(anchorName));
336 m_sectionAnchors.insert("\\begin{" + word, Anchor(sectionAnchorName));
337 } else {
338 // TODO: anything useful in the rest?
339 //qDebug() << word << anchorName << sectionAnchorName << sectionTitle;
340 }
341 pos += match.capturedLength();
342 }
343 //qDebug() << "Found entries in index:" << m_anchors.count();
344 }
345
346