]> git.lyx.org Git - lyx.git/blob - src/Session.cpp
Avoid duplicates in minibuffer history
[lyx.git] / src / Session.cpp
1 /**
2  * \file Session.cpp
3  * This file is part of LyX, the document processor.
4  * Licence details can be found in the file COPYING.
5  *
6  * \author Lars Gullik Bjønnes
7  * \author Bo Peng
8  *
9  * Full author contact details are available in file CREDITS.
10  */
11
12 #include <config.h>
13
14 #include "Session.h"
15
16 #include "support/debug.h"
17 #include "support/filetools.h"
18 #include "support/Package.h"
19
20 #include <fstream>
21 #include <sstream>
22 #include <algorithm>
23 #include <iterator>
24
25 using namespace std;
26 using namespace lyx::support;
27
28 namespace {
29
30 string const sec_lastfiles = "[recent files]";
31 string const sec_lastfilepos = "[cursor positions]";
32 string const sec_lastopened = "[last opened files]";
33 string const sec_bookmarks = "[bookmarks]";
34 string const sec_session = "[session info]";
35 string const sec_toolbars = "[toolbars]";
36 string const sec_lastcommands = "[last commands]";
37 string const sec_authfiles = "[auth files]";
38 string const sec_shellescape = "[shell escape files]";
39
40 } // namespace
41
42
43 namespace lyx {
44
45 LastFilesSection::LastFilesSection(unsigned int num) :
46         default_num_last_files(4),
47         absolute_max_last_files(100)
48 {
49         setNumberOfLastFiles(num);
50 }
51
52
53 void LastFilesSection::read(istream & is)
54 {
55         string tmp;
56         do {
57                 char c = is.peek();
58                 if (c == '[')
59                         break;
60                 getline(is, tmp);
61                 if (tmp.empty() || tmp[0] == '#' || tmp[0] == ' ' || !FileName::isAbsolute(tmp))
62                         continue;
63
64                 // read lastfiles
65                 FileName const file(tmp);
66                 if (file.exists() && !file.isDirectory()
67                     && lastfiles.size() < num_lastfiles)
68                         lastfiles.push_back(file);
69                 else
70                         LYXERR(Debug::INIT, "LyX: Warning: Ignore last file: " << tmp);
71         } while (is.good());
72 }
73
74
75 void LastFilesSection::write(ostream & os) const
76 {
77         os << '\n' << sec_lastfiles << '\n';
78         copy(lastfiles.begin(), lastfiles.end(),
79              ostream_iterator<FileName>(os, "\n"));
80 }
81
82
83 void LastFilesSection::add(FileName const & file)
84 {
85         // If file already exist, delete it and reinsert at front.
86         LastFiles::iterator it = find(lastfiles.begin(), lastfiles.end(), file);
87         if (it != lastfiles.end())
88                 lastfiles.erase(it);
89         lastfiles.insert(lastfiles.begin(), file);
90         if (lastfiles.size() > num_lastfiles)
91                 lastfiles.pop_back();
92 }
93
94
95 void LastFilesSection::setNumberOfLastFiles(unsigned int no)
96 {
97         if (0 < no && no <= absolute_max_last_files)
98                 num_lastfiles = no;
99         else {
100                 LYXERR(Debug::INIT, "LyX: session: too many last files\n"
101                         << "\tdefault (=" << default_num_last_files << ") used.");
102                 num_lastfiles = default_num_last_files;
103         }
104 }
105
106
107 void LastOpenedSection::read(istream & is)
108 {
109         string tmp;
110         do {
111                 char c = is.peek();
112                 if (c == '[')
113                         break;
114                 getline(is, tmp);
115                 if (tmp.empty() || tmp[0] == '#' || tmp[0] == ' ')
116                         continue;
117
118                 try {
119                         LastOpenedFile lof;
120                         istringstream itmp(tmp);
121                         itmp >> lof.active;
122                         itmp.ignore(2);  // ignore ", "
123                         string fname;
124                         getline(itmp, fname);
125                         if (!FileName::isAbsolute(fname))
126                                 continue;
127
128                         FileName const file(fname);
129                         if (file.exists() && !file.isDirectory()) {
130                                 lof.file_name = file;
131                                 lastopened.push_back(lof);
132                         } else {
133                                 LYXERR(Debug::INIT,
134                                         "LyX: Warning: Ignore last opened file: " << tmp);
135                         }
136                 } catch (...) {
137                         LYXERR(Debug::INIT,
138                                 "LyX: Warning: unknown state of last opened file: " << tmp);
139                 }
140         } while (is.good());
141 }
142
143
144 void LastOpenedSection::write(ostream & os) const
145 {
146         os << '\n' << sec_lastopened << '\n';
147         for (auto const & last : lastopened)
148                 os << last.active << ", " << last.file_name << '\n';
149 }
150
151
152 void LastOpenedSection::add(FileName const & file, bool active)
153 {
154         LastOpenedFile lof(file, active);
155         // check if file is already recorded (this can happen
156         // with multiple buffer views). We do only record each
157         // file once, since we cannot restore multiple views
158         // currently, we even crash in some cases (see #9483).
159         // FIXME: Add session support for multiple views of
160         //        the same buffer (split-view etc.).
161         for (auto const & last : lastopened) {
162                 if (last.file_name == file)
163                         return;
164         }
165         lastopened.push_back(lof);
166 }
167
168
169 void LastOpenedSection::clear()
170 {
171         lastopened.clear();
172 }
173
174
175 void LastFilePosSection::read(istream & is)
176 {
177         string tmp;
178         do {
179                 char c = is.peek();
180                 if (c == '[')
181                         break;
182                 getline(is, tmp);
183                 if (tmp == "" || tmp[0] == '#' || tmp[0] == ' ')
184                         continue;
185
186                 try {
187                         // read lastfilepos
188                         // pos, file\n
189                         FilePos filepos;
190                         string fname;
191                         istringstream itmp(tmp);
192                         itmp >> filepos.pit;
193                         itmp.ignore(2);  // ignore ", "
194                         itmp >> filepos.pos;
195                         itmp.ignore(2);  // ignore ", "
196                         getline(itmp, fname);
197                         if (!FileName::isAbsolute(fname))
198                                 continue;
199                         filepos.file = FileName(fname);
200                         if (filepos.file.exists() && !filepos.file.isDirectory()
201                             && lastfilepos.size() < num_lastfilepos)
202                                 lastfilepos.push_back(filepos);
203                         else
204                                 LYXERR(Debug::INIT, "LyX: Warning: Ignore pos of last file: " << fname);
205                 } catch (...) {
206                         LYXERR(Debug::INIT, "LyX: Warning: unknown pos of last file: " << tmp);
207                 }
208         } while (is.good());
209 }
210
211
212 void LastFilePosSection::write(ostream & os) const
213 {
214         os << '\n' << sec_lastfilepos << '\n';
215         for (auto const & file_p : lastfilepos)
216                 os << file_p.pit << ", " << file_p.pos << ", " << file_p.file << '\n';
217 }
218
219
220 void LastFilePosSection::save(FilePos const & pos)
221 {
222         // Remove element if it was already present. Iterating should
223         // not be a problem since the list is small (<100 elements).
224         for (FilePosList::iterator it = lastfilepos.begin();
225              it != lastfilepos.end(); ++it)
226                 if (it->file == pos.file) {
227                         lastfilepos.erase(it);
228                         break;
229                 }
230
231         // insert new element at front.
232         lastfilepos.push_front(pos);
233 }
234
235
236 LastFilePosSection::FilePos LastFilePosSection::load(FileName const & fname) const
237 {
238         for (auto const & fp : lastfilepos)
239                 if (fp.file == fname)
240                         // Has position information, return it.
241                         return fp;
242
243         // Not found, return the first paragraph
244         return FilePos();
245 }
246
247
248 void BookmarksSection::clear()
249 {
250         // keep bookmark[0], the temporary one
251         bookmarks.resize(1);
252         bookmarks.resize(max_bookmarks + 1);
253 }
254
255
256 void BookmarksSection::read(istream & is)
257 {
258         string tmp;
259         do {
260                 char c = is.peek();
261                 if (c == '[')
262                         break;
263                 getline(is, tmp);
264                 if (tmp == "" || tmp[0] == '#' || tmp[0] == ' ')
265                         continue;
266
267                 try {
268                         // read bookmarks
269                         // idx, pit, pos, file\n
270                         unsigned int idx;
271                         pit_type pit;
272                         pos_type pos;
273                         string fname;
274                         istringstream itmp(tmp);
275                         itmp >> idx;
276                         itmp.ignore(2);  // ignore ", "
277                         itmp >> pit;
278                         itmp.ignore(2);  // ignore ", "
279                         itmp >> pos;
280                         itmp.ignore(2);  // ignore ", "
281                         getline(itmp, fname);
282                         if (!FileName::isAbsolute(fname))
283                                 continue;
284                         FileName const file(fname);
285                         // only load valid bookmarks
286                         if (file.exists() && !file.isDirectory() && idx < bookmarks.size())
287                                 bookmarks[idx] = Bookmark(file, pit, pos, 0, 0);
288                         else
289                                 LYXERR(Debug::INIT, "LyX: Warning: Ignore bookmark of file: " << fname);
290                 } catch (...) {
291                         LYXERR(Debug::INIT, "LyX: Warning: unknown Bookmark info: " << tmp);
292                 }
293         } while (is.good());
294 }
295
296
297 void BookmarksSection::write(ostream & os) const
298 {
299         os << '\n' << sec_bookmarks << '\n';
300         for (size_t i = 0; i < bookmarks.size(); ++i) {
301                 if (isValid(i))
302                         os << i << ", "
303                            << bookmarks[i].bottom_pit << ", "
304                            << bookmarks[i].bottom_pos << ", "
305                            << bookmarks[i].filename << '\n';
306         }
307 }
308
309
310 void BookmarksSection::save(FileName const & fname,
311         pit_type bottom_pit, pos_type bottom_pos,
312         int top_id, pos_type top_pos, unsigned int idx)
313 {
314         // silently ignore bookmarks when idx is out of range
315         if (idx < bookmarks.size())
316                 bookmarks[idx] = Bookmark(fname, bottom_pit, bottom_pos, top_id, top_pos);
317 }
318
319
320 bool BookmarksSection::isValid(unsigned int i) const
321 {
322         return i < bookmarks.size() && !bookmarks[i].filename.empty();
323 }
324
325
326 bool BookmarksSection::hasValid() const
327 {
328         for (size_t i = 1; i < bookmarks.size(); ++i) {
329                 if (isValid(i))
330                         return true;
331         }
332         return false;
333 }
334
335
336 BookmarksSection::Bookmark const & BookmarksSection::bookmark(unsigned int i) const
337 {
338         return bookmarks[i];
339 }
340
341
342 BookmarksSection::BookmarkPosList
343 BookmarksSection::bookmarksInPar(FileName const & fn, int const par_id) const
344 {
345         // FIXME: we do not consider the case of bottom_pit.
346         // This is probably not a problem.
347         BookmarksSection::BookmarkPosList bip;
348         for (size_t i = 1; i < bookmarks.size(); ++i)
349                 if (bookmarks[i].filename == fn && bookmarks[i].top_id == par_id)
350                         bip.push_back({i, bookmarks[i].top_pos});
351
352         return bip;
353 }
354
355
356 void BookmarksSection::adjustPosAfterPos(FileName const & fn,
357         int const par_id, pos_type pos, int offset)
358 {
359         for (Bookmark & bkm : bookmarks)
360                 if (bkm.filename == fn && bkm.top_id == par_id && bkm.top_pos > pos)
361                         bkm.top_pos += offset;
362 }
363
364
365 LastCommandsSection::LastCommandsSection(unsigned int num) :
366         default_num_last_commands(30),
367         absolute_max_last_commands(100)
368 {
369         setNumberOfLastCommands(num);
370 }
371
372
373 void LastCommandsSection::read(istream & is)
374 {
375         string tmp;
376         do {
377                 char c = is.peek();
378                 if (c == '[')
379                         break;
380                 getline(is, tmp);
381                 if (tmp == "" || tmp[0] == '#' || tmp[0] == ' ')
382                         continue;
383
384                 lastcommands.push_back(tmp);
385         } while (is.good());
386 }
387
388
389 void LastCommandsSection::write(ostream & os) const
390 {
391         os << '\n' << sec_lastcommands << '\n';
392         copy(lastcommands.begin(), lastcommands.end(),
393                 ostream_iterator<std::string>(os, "\n"));
394 }
395
396
397 void LastCommandsSection::setNumberOfLastCommands(unsigned int no)
398 {
399         if (0 < no && no <= absolute_max_last_commands)
400                 num_lastcommands = no;
401         else {
402                 LYXERR(Debug::INIT, "LyX: session: too many last commands\n"
403                         << "\tdefault (=" << default_num_last_commands << ") used.");
404                 num_lastcommands = default_num_last_commands;
405         }
406 }
407
408
409 void LastCommandsSection::add(std::string const & command)
410 {
411         // remove traces of 'command' in history using the erase-remove idiom
412         //   https://en.wikipedia.org/wiki/Erase%E2%80%93remove_idiom
413         lastcommands.erase(remove(lastcommands.begin(), lastcommands.end(), command),
414                            lastcommands.end());
415         // add it at the end of the list.
416         lastcommands.push_back(command);
417 }
418
419
420 void LastCommandsSection::clear()
421 {
422         lastcommands.clear();
423 }
424
425
426 Session::Session(unsigned int num_last_files, unsigned int num_last_commands) :
427         last_files(num_last_files), last_commands(num_last_commands)
428 {
429         // locate the session file
430         // note that the session file name 'session' is hard-coded
431         session_file = FileName(addName(package().user_support().absFileName(), "session"));
432         //
433         readFile();
434 }
435
436
437 void Session::readFile()
438 {
439         // we will not complain if we can't find session_file nor will
440         // we issue a warning. (Lgb)
441         ifstream is(session_file.toFilesystemEncoding().c_str());
442         string tmp;
443
444         while (getline(is, tmp)) {
445                 // Ignore comments, empty line or line stats with ' '
446                 if (tmp == "" || tmp[0] == '#' || tmp[0] == ' ')
447                         continue;
448
449                 // Determine section id
450                 if (tmp == sec_lastfiles)
451                         lastFiles().read(is);
452                 else if (tmp == sec_lastopened)
453                         lastOpened().read(is);
454                 else if (tmp == sec_lastfilepos)
455                         lastFilePos().read(is);
456                 else if (tmp == sec_bookmarks)
457                         bookmarks().read(is);
458                 else if (tmp == sec_lastcommands)
459                         lastCommands().read(is);
460                 else if (tmp == sec_authfiles)
461                         authFiles().read(is);
462                 else if (tmp == sec_shellescape)
463                         shellescapeFiles().read(is);
464
465                 else
466                         LYXERR(Debug::INIT, "LyX: Warning: unknown Session section: " << tmp);
467         }
468 }
469
470
471 void Session::writeFile() const
472 {
473         ofstream os(session_file.toFilesystemEncoding().c_str());
474         if (os) {
475                 os << "## Automatically generated lyx session file \n"
476                     << "## Editing this file manually may cause lyx to crash.\n";
477
478                 lastFiles().write(os);
479                 lastOpened().write(os);
480                 lastFilePos().write(os);
481                 lastCommands().write(os);
482                 bookmarks().write(os);
483                 authFiles().write(os);
484                 shellescapeFiles().write(os);
485         } else
486                 LYXERR(Debug::INIT, "LyX: Warning: unable to save Session: "
487                        << session_file);
488 }
489
490
491 AuthFilesSection::AuthFilesSection() {  }
492
493
494 void AuthFilesSection::read(istream & is)
495 {
496         string tmp;
497         do {
498                 char c = is.peek();
499                 if (c == '[')
500                         break;
501                 getline(is, tmp);
502                 if (tmp.empty() || tmp[0] == '#' || tmp[0] == ' ' || !FileName::isAbsolute(tmp))
503                         continue;
504
505                 // read lastfiles
506                 FileName const file(tmp);
507                 if (file.exists() && !file.isDirectory())
508                         auth_files_.insert(tmp);
509                 else
510                         LYXERR(Debug::INIT, "LyX: Warning: Ignore auth file: " << tmp);
511         } while (is.good());
512 }
513
514
515 void AuthFilesSection::write(ostream & os) const
516 {
517         os << '\n' << sec_authfiles << '\n';
518         copy(auth_files_.begin(), auth_files_.end(),
519              ostream_iterator<std::string>(os, "\n"));
520 }
521
522
523 bool AuthFilesSection::find(string const & name) const
524 {
525         return auth_files_.find(name) != auth_files_.end();
526 }
527
528
529 void AuthFilesSection::insert(string const & name)
530 {
531         auth_files_.insert(name);
532 }
533
534
535 void ShellEscapeSection::read(istream & is)
536 {
537         string s;
538         do {
539                 char c = is.peek();
540                 if (c == '[')
541                         break;
542                 getline(is, s);
543                 if (s.empty() || s[0] == '#' || s[0] == ' ' || !FileName::isAbsolute(s))
544                         continue;
545
546                 // read shellescape files
547                 FileName const file(s.substr(0, s.length() - 2));
548                 if (file.exists() && !file.isDirectory())
549                         shellescape_files_.insert(s);
550                 else
551                         LYXERR(Debug::INIT, "LyX: Warning: Ignore shellescape file: " << file);
552         } while (is.good());
553 }
554
555
556 void ShellEscapeSection::write(ostream & os) const
557 {
558         os << '\n' << sec_shellescape << '\n';
559         copy(shellescape_files_.begin(), shellescape_files_.end(),
560              ostream_iterator<std::string>(os, "\n"));
561 }
562
563
564 bool ShellEscapeSection::find(string const & name) const
565 {
566         if (shellescape_files_.find(name + ",0") != shellescape_files_.end())
567                 return true;
568
569         return findAuth(name);
570 }
571
572
573 bool ShellEscapeSection::findAuth(string const & name) const
574 {
575         return shellescape_files_.find(name + ",1") != shellescape_files_.end();
576 }
577
578
579 void ShellEscapeSection::insert(string const & name, bool auth)
580 {
581         set<string>::iterator it;
582         string const name0 = name + ",0";
583         string const name1 = name + ",1";
584
585         if (auth) {
586                 it = shellescape_files_.find(name0);
587                 if (it != shellescape_files_.end())
588                         shellescape_files_.erase(it);
589                 shellescape_files_.insert(name1);
590         } else {
591                 it = shellescape_files_.find(name1);
592                 if (it != shellescape_files_.end())
593                         shellescape_files_.erase(it);
594                 shellescape_files_.insert(name0);
595         }
596 }
597
598
599 void ShellEscapeSection::remove(string const & name)
600 {
601         set<string>::iterator it = shellescape_files_.find(name + ",0");
602         if (it == shellescape_files_.end())
603                 it = shellescape_files_.find(name + ",1");
604         if (it != shellescape_files_.end())
605                 shellescape_files_.erase(it);
606 }
607
608
609 } // namespace lyx