Another minor change, but this should almost get us to the point that we
[lyx.git] / src / LyXFunc.cpp
blobcc213313e61198926aff2a835591644bb783908b
1 /**
2 * \file LyXFunc.cpp
3 * This file is part of LyX, the document processor.
4 * Licence details can be found in the file COPYING.
6 * \author Alfredo Braunstein
7 * \author Lars Gullik Bjønnes
8 * \author Jean-Marc Lasgouttes
9 * \author Angus Leeming
10 * \author John Levon
11 * \author André Pönitz
12 * \author Allan Rae
13 * \author Dekel Tsur
14 * \author Martin Vermeer
15 * \author Jürgen Vigna
17 * Full author contact details are available in file CREDITS.
20 #include <config.h>
22 #include "LyXFunc.h"
24 #include "LayoutFile.h"
25 #include "BranchList.h"
26 #include "buffer_funcs.h"
27 #include "Buffer.h"
28 #include "BufferList.h"
29 #include "BufferParams.h"
30 #include "BufferView.h"
31 #include "CmdDef.h"
32 #include "Color.h"
33 #include "Converter.h"
34 #include "Cursor.h"
35 #include "CutAndPaste.h"
36 #include "DispatchResult.h"
37 #include "Encoding.h"
38 #include "ErrorList.h"
39 #include "Format.h"
40 #include "FuncRequest.h"
41 #include "FuncStatus.h"
42 #include "InsetIterator.h"
43 #include "KeyMap.h"
44 #include "Language.h"
45 #include "Lexer.h"
46 #include "LyXAction.h"
47 #include "lyxfind.h"
48 #include "LyX.h"
49 #include "LyXRC.h"
50 #include "LyXVC.h"
51 #include "Paragraph.h"
52 #include "ParagraphParameters.h"
53 #include "ParIterator.h"
54 #include "Row.h"
55 #include "Session.h"
56 #include "SpellChecker.h"
58 #include "frontends/alert.h"
59 #include "frontends/Application.h"
60 #include "frontends/KeySymbol.h"
61 #include "frontends/LyXView.h"
62 #include "frontends/Selection.h"
64 #include "support/debug.h"
65 #include "support/environment.h"
66 #include "support/FileName.h"
67 #include "support/filetools.h"
68 #include "support/gettext.h"
69 #include "support/lassert.h"
70 #include "support/lstrings.h"
71 #include "support/Package.h"
72 #include "support/convert.h"
73 #include "support/os.h"
75 #include <sstream>
76 #include <vector>
78 using namespace std;
79 using namespace lyx::support;
81 namespace lyx {
83 using frontend::LyXView;
85 namespace Alert = frontend::Alert;
87 LyXFunc::LyXFunc()
92 //FIXME: bookmark handling is a frontend issue. This code should be transferred
93 // to GuiView and be GuiView and be window dependent.
94 void LyXFunc::gotoBookmark(unsigned int idx, bool openFile, bool switchToBuffer)
96 LyXView * lv = theApp()->currentWindow();
97 LASSERT(lv, /**/);
98 if (!theSession().bookmarks().isValid(idx))
99 return;
100 BookmarksSection::Bookmark const & bm = theSession().bookmarks().bookmark(idx);
101 LASSERT(!bm.filename.empty(), /**/);
102 string const file = bm.filename.absFilename();
103 // if the file is not opened, open it.
104 if (!theBufferList().exists(bm.filename)) {
105 if (openFile)
106 dispatch(FuncRequest(LFUN_FILE_OPEN, file));
107 else
108 return;
110 // open may fail, so we need to test it again
111 if (!theBufferList().exists(bm.filename))
112 return;
114 // bm can be changed when saving
115 BookmarksSection::Bookmark tmp = bm;
117 // Special case idx == 0 used for back-from-back jump navigation
118 if (idx == 0)
119 dispatch(FuncRequest(LFUN_BOOKMARK_SAVE, "0"));
121 // if the current buffer is not that one, switch to it.
122 if (!lv->documentBufferView()
123 || lv->documentBufferView()->buffer().fileName() != tmp.filename) {
124 if (!switchToBuffer)
125 return;
126 dispatch(FuncRequest(LFUN_BUFFER_SWITCH, file));
129 // moveToPosition try paragraph id first and then paragraph (pit, pos).
130 if (!lv->documentBufferView()->moveToPosition(
131 tmp.bottom_pit, tmp.bottom_pos, tmp.top_id, tmp.top_pos))
132 return;
134 // bm changed
135 if (idx == 0)
136 return;
138 // Cursor jump succeeded!
139 Cursor const & cur = lv->documentBufferView()->cursor();
140 pit_type new_pit = cur.pit();
141 pos_type new_pos = cur.pos();
142 int new_id = cur.paragraph().id();
144 // if bottom_pit, bottom_pos or top_id has been changed, update bookmark
145 // see http://bugzilla.lyx.org/show_bug.cgi?id=3092
146 if (bm.bottom_pit != new_pit || bm.bottom_pos != new_pos
147 || bm.top_id != new_id) {
148 const_cast<BookmarksSection::Bookmark &>(bm).updatePos(
149 new_pit, new_pos, new_id);
154 FuncStatus LyXFunc::getStatus(FuncRequest const & cmd) const
156 //lyxerr << "LyXFunc::getStatus: cmd: " << cmd << endl;
157 FuncStatus flag;
159 if (cmd.action == LFUN_NOACTION) {
160 flag.message(from_utf8(N_("Nothing to do")));
161 flag.setEnabled(false);
162 return flag;
165 if (cmd.action == LFUN_UNKNOWN_ACTION) {
166 flag.unknown(true);
167 flag.setEnabled(false);
168 flag.message(from_utf8(N_("Unknown action")));
169 return flag;
172 // I would really like to avoid having this switch and rather try to
173 // encode this in the function itself.
174 // -- And I'd rather let an inset decide which LFUNs it is willing
175 // to handle (Andre')
176 bool enable = true;
177 switch (cmd.action) {
179 // This could be used for the no-GUI version. The GUI version is handled in
180 // LyXView::getStatus(). See above.
182 case LFUN_BUFFER_WRITE:
183 case LFUN_BUFFER_WRITE_AS: {
184 Buffer * b = theBufferList().getBuffer(FileName(cmd.getArg(0)));
185 enable = b && (b->isUnnamed() || !b->isClean());
186 break;
190 case LFUN_BOOKMARK_GOTO: {
191 const unsigned int num = convert<unsigned int>(to_utf8(cmd.argument()));
192 enable = theSession().bookmarks().isValid(num);
193 break;
196 case LFUN_BOOKMARK_CLEAR:
197 enable = theSession().bookmarks().hasValid();
198 break;
200 // this one is difficult to get right. As a half-baked
201 // solution, we consider only the first action of the sequence
202 case LFUN_COMMAND_SEQUENCE: {
203 // argument contains ';'-terminated commands
204 string const firstcmd = token(to_utf8(cmd.argument()), ';', 0);
205 FuncRequest func(lyxaction.lookupFunc(firstcmd));
206 func.origin = cmd.origin;
207 flag = getStatus(func);
208 break;
211 // we want to check if at least one of these is enabled
212 case LFUN_COMMAND_ALTERNATIVES: {
213 // argument contains ';'-terminated commands
214 string arg = to_utf8(cmd.argument());
215 while (!arg.empty()) {
216 string first;
217 arg = split(arg, first, ';');
218 FuncRequest func(lyxaction.lookupFunc(first));
219 func.origin = cmd.origin;
220 flag = getStatus(func);
221 // if this one is enabled, the whole thing is
222 if (flag.enabled())
223 break;
225 break;
228 case LFUN_CALL: {
229 FuncRequest func;
230 string name = to_utf8(cmd.argument());
231 if (theTopLevelCmdDef().lock(name, func)) {
232 func.origin = cmd.origin;
233 flag = getStatus(func);
234 theTopLevelCmdDef().release(name);
235 } else {
236 // catch recursion or unknown command
237 // definition. all operations until the
238 // recursion or unknown command definition
239 // occurs are performed, so set the state to
240 // enabled
241 enable = true;
243 break;
246 case LFUN_CURSOR_FOLLOWS_SCROLLBAR_TOGGLE:
247 case LFUN_REPEAT:
248 case LFUN_PREFERENCES_SAVE:
249 case LFUN_BUFFER_SAVE_AS_DEFAULT:
250 // these are handled in our dispatch()
251 break;
253 default:
254 if (!theApp()) {
255 enable = false;
256 break;
258 if (theApp()->getStatus(cmd, flag))
259 break;
261 // Does the view know something?
262 LyXView * lv = theApp()->currentWindow();
263 if (!lv) {
264 enable = false;
265 break;
267 if (lv->getStatus(cmd, flag))
268 break;
270 BufferView * bv = lv->currentBufferView();
271 BufferView * doc_bv = lv->documentBufferView();
272 // If we do not have a BufferView, then other functions are disabled
273 if (!bv) {
274 enable = false;
275 break;
277 // try the BufferView
278 bool decided = bv->getStatus(cmd, flag);
279 if (!decided)
280 // try the Buffer
281 decided = bv->buffer().getStatus(cmd, flag);
282 if (!decided && doc_bv)
283 // try the Document Buffer
284 decided = doc_bv->buffer().getStatus(cmd, flag);
287 if (!enable)
288 flag.setEnabled(false);
290 // the default error message if we disable the command
291 if (!flag.enabled() && flag.message().empty())
292 flag.message(from_utf8(N_("Command disabled")));
294 return flag;
297 /// send a post-dispatch status message
298 static docstring sendDispatchMessage(docstring const & msg, FuncRequest const & cmd)
300 const bool verbose = (cmd.origin == FuncRequest::MENU
301 || cmd.origin == FuncRequest::TOOLBAR
302 || cmd.origin == FuncRequest::COMMANDBUFFER);
304 if (cmd.action == LFUN_SELF_INSERT || !verbose) {
305 LYXERR(Debug::ACTION, "dispatch msg is " << msg);
306 return msg;
309 docstring dispatch_msg = msg;
310 if (!dispatch_msg.empty())
311 dispatch_msg += ' ';
313 docstring comname = from_utf8(lyxaction.getActionName(cmd.action));
315 bool argsadded = false;
317 if (!cmd.argument().empty()) {
318 if (cmd.action != LFUN_UNKNOWN_ACTION) {
319 comname += ' ' + cmd.argument();
320 argsadded = true;
323 docstring const shortcuts = theTopLevelKeymap().
324 printBindings(cmd, KeySequence::ForGui);
326 if (!shortcuts.empty())
327 comname += ": " + shortcuts;
328 else if (!argsadded && !cmd.argument().empty())
329 comname += ' ' + cmd.argument();
331 if (!comname.empty()) {
332 comname = rtrim(comname);
333 dispatch_msg += '(' + rtrim(comname) + ')';
335 LYXERR(Debug::ACTION, "verbose dispatch msg " << to_utf8(dispatch_msg));
336 return dispatch_msg;
340 void LyXFunc::dispatch(FuncRequest const & cmd)
342 string const argument = to_utf8(cmd.argument());
343 FuncCode const action = cmd.action;
345 LYXERR(Debug::ACTION, "\nLyXFunc::dispatch: cmd: " << cmd);
346 //lyxerr << "LyXFunc::dispatch: cmd: " << cmd << endl;
348 // we have not done anything wrong yet.
349 errorstat = false;
350 dispatch_buffer.erase();
352 // redraw the screen at the end (first of the two drawing steps).
353 //This is done unless explicitely requested otherwise
354 Update::flags updateFlags = Update::FitCursor;
356 LyXView * lv = theApp()->currentWindow();
358 FuncStatus const flag = getStatus(cmd);
359 if (!flag.enabled()) {
360 // We cannot use this function here
361 LYXERR(Debug::ACTION, "LyXFunc::dispatch: "
362 << lyxaction.getActionName(action)
363 << " [" << action << "] is disabled at this location");
364 setErrorMessage(flag.message());
365 if (lv)
366 lv->restartCursor();
367 } else {
368 switch (action) {
370 case LFUN_CURSOR_FOLLOWS_SCROLLBAR_TOGGLE:
371 lyxrc.cursor_follows_scrollbar = !lyxrc.cursor_follows_scrollbar;
372 break;
374 case LFUN_REPEAT: {
375 // repeat command
376 string countstr;
377 string rest = split(argument, countstr, ' ');
378 istringstream is(countstr);
379 int count = 0;
380 is >> count;
381 //lyxerr << "repeat: count: " << count << " cmd: " << rest << endl;
382 for (int i = 0; i < count; ++i)
383 dispatch(lyxaction.lookupFunc(rest));
384 break;
387 case LFUN_COMMAND_SEQUENCE: {
388 // argument contains ';'-terminated commands
389 string arg = argument;
390 // FIXME: this LFUN should also work without any view.
391 Buffer * buffer = (lv && lv->documentBufferView())
392 ? &(lv->documentBufferView()->buffer()) : 0;
393 if (buffer)
394 buffer->undo().beginUndoGroup();
395 while (!arg.empty()) {
396 string first;
397 arg = split(arg, first, ';');
398 FuncRequest func(lyxaction.lookupFunc(first));
399 func.origin = cmd.origin;
400 dispatch(func);
402 // the buffer may have been closed by one action
403 if (theBufferList().isLoaded(buffer))
404 buffer->undo().endUndoGroup();
405 break;
408 case LFUN_COMMAND_ALTERNATIVES: {
409 // argument contains ';'-terminated commands
410 string arg = argument;
411 while (!arg.empty()) {
412 string first;
413 arg = split(arg, first, ';');
414 FuncRequest func(lyxaction.lookupFunc(first));
415 func.origin = cmd.origin;
416 FuncStatus stat = getStatus(func);
417 if (stat.enabled()) {
418 dispatch(func);
419 break;
422 break;
425 case LFUN_CALL: {
426 FuncRequest func;
427 if (theTopLevelCmdDef().lock(argument, func)) {
428 func.origin = cmd.origin;
429 dispatch(func);
430 theTopLevelCmdDef().release(argument);
431 } else {
432 if (func.action == LFUN_UNKNOWN_ACTION) {
433 // unknown command definition
434 lyxerr << "Warning: unknown command definition `"
435 << argument << "'"
436 << endl;
437 } else {
438 // recursion detected
439 lyxerr << "Warning: Recursion in the command definition `"
440 << argument << "' detected"
441 << endl;
444 break;
447 case LFUN_PREFERENCES_SAVE: {
448 lyxrc.write(makeAbsPath("preferences",
449 package().user_support().absFilename()),
450 false);
451 break;
454 case LFUN_BUFFER_SAVE_AS_DEFAULT: {
455 string const fname =
456 addName(addPath(package().user_support().absFilename(), "templates/"),
457 "defaults.lyx");
458 Buffer defaults(fname);
460 istringstream ss(argument);
461 Lexer lex;
462 lex.setStream(ss);
463 int const unknown_tokens = defaults.readHeader(lex);
465 if (unknown_tokens != 0) {
466 lyxerr << "Warning in LFUN_BUFFER_SAVE_AS_DEFAULT!\n"
467 << unknown_tokens << " unknown token"
468 << (unknown_tokens == 1 ? "" : "s")
469 << endl;
472 if (defaults.writeFile(FileName(defaults.absFileName())))
473 setMessage(bformat(_("Document defaults saved in %1$s"),
474 makeDisplayPath(fname)));
475 else
476 setErrorMessage(from_ascii(N_("Unable to save document defaults")));
477 break;
480 case LFUN_BOOKMARK_GOTO:
481 // go to bookmark, open unopened file and switch to buffer if necessary
482 gotoBookmark(convert<unsigned int>(to_utf8(cmd.argument())), true, true);
483 updateFlags = Update::FitCursor;
484 break;
486 case LFUN_BOOKMARK_CLEAR:
487 theSession().bookmarks().clear();
488 break;
490 default:
491 DispatchResult dr;
493 LASSERT(theApp(), /**/);
494 // Let the frontend dispatch its own actions.
495 theApp()->dispatch(cmd, dr);
496 if (dr.dispatched())
497 // Nothing more to do.
498 break;
500 // Everything below is only for active window
501 if (lv == 0)
502 break;
504 // Let the current LyXView dispatch its own actions.
505 if (lv->dispatch(cmd)) {
506 BufferView * bv = lv->currentBufferView();
507 if (bv)
508 updateFlags = bv->cursor().result().update();
509 break;
512 BufferView * bv = lv->currentBufferView();
513 LASSERT(bv, /**/);
515 // Let the current BufferView dispatch its own actions.
516 if (bv->dispatch(cmd)) {
517 // The BufferView took care of its own updates if needed.
518 updateFlags = Update::None;
519 break;
522 BufferView * doc_bv = lv->documentBufferView();
523 // Try with the document BufferView dispatch if any.
524 if (doc_bv && doc_bv->dispatch(cmd)) {
525 updateFlags = Update::None;
526 break;
529 // OK, so try the current Buffer itself...
530 bv->buffer().dispatch(cmd, dr);
531 if (dr.dispatched()) {
532 updateFlags = dr.update();
533 break;
535 // and with the document Buffer.
536 if (doc_bv) {
537 doc_bv->buffer().dispatch(cmd, dr);
538 if (dr.dispatched()) {
539 updateFlags = dr.update();
540 break;
544 // Is this a function that acts on inset at point?
545 Inset * inset = bv->cursor().nextInset();
546 if (lyxaction.funcHasFlag(action, LyXAction::AtPoint)
547 && inset) {
548 bv->cursor().result().dispatched(true);
549 bv->cursor().result().update(Update::FitCursor | Update::Force);
550 FuncRequest tmpcmd = cmd;
551 inset->dispatch(bv->cursor(), tmpcmd);
552 if (bv->cursor().result().dispatched()) {
553 updateFlags = bv->cursor().result().update();
554 break;
558 // Let the current Cursor dispatch its own actions.
559 Cursor old = bv->cursor();
560 bv->cursor().getPos(cursorPosBeforeDispatchX_,
561 cursorPosBeforeDispatchY_);
562 bv->cursor().dispatch(cmd);
564 // notify insets we just left
565 if (bv->cursor() != old) {
566 old.fixIfBroken();
567 bool badcursor = notifyCursorLeavesOrEnters(old, bv->cursor());
568 if (badcursor)
569 bv->cursor().fixIfBroken();
572 // update completion. We do it here and not in
573 // processKeySym to avoid another redraw just for a
574 // changed inline completion
575 if (cmd.origin == FuncRequest::KEYBOARD) {
576 if (cmd.action == LFUN_SELF_INSERT
577 || (cmd.action == LFUN_ERT_INSERT && bv->cursor().inMathed()))
578 lv->updateCompletion(bv->cursor(), true, true);
579 else if (cmd.action == LFUN_CHAR_DELETE_BACKWARD)
580 lv->updateCompletion(bv->cursor(), false, true);
581 else
582 lv->updateCompletion(bv->cursor(), false, false);
585 updateFlags = bv->cursor().result().update();
588 // if we executed a mutating lfun, mark the buffer as dirty
589 Buffer * doc_buffer = (lv && lv->documentBufferView())
590 ? &(lv->documentBufferView()->buffer()) : 0;
591 if (doc_buffer && theBufferList().isLoaded(doc_buffer)
592 && flag.enabled()
593 && !lyxaction.funcHasFlag(action, LyXAction::NoBuffer)
594 && !lyxaction.funcHasFlag(action, LyXAction::ReadOnly))
595 doc_buffer->markDirty();
597 if (lv && lv->currentBufferView()) {
598 // BufferView::update() updates the ViewMetricsInfo and
599 // also initializes the position cache for all insets in
600 // (at least partially) visible top-level paragraphs.
601 // We will redraw the screen only if needed.
602 lv->currentBufferView()->processUpdateFlags(updateFlags);
604 // Do we have a selection?
605 theSelection().haveSelection(
606 lv->currentBufferView()->cursor().selection());
608 // update gui
609 lv->restartCursor();
612 if (lv) {
613 // Some messages may already be translated, so we cannot use _()
614 lv->message(sendDispatchMessage(
615 translateIfPossible(getMessage()), cmd));
620 // Each LyXView should have it's own message method. lyxview and
621 // the minibuffer would use the minibuffer, but lyxserver would
622 // send an ERROR signal to its client. Alejandro 970603
623 // This function is bit problematic when it comes to NLS, to make the
624 // lyx servers client be language indepenent we must not translate
625 // strings sent to this func.
626 void LyXFunc::setErrorMessage(docstring const & m) const
628 dispatch_buffer = m;
629 errorstat = true;
633 void LyXFunc::setMessage(docstring const & m) const
635 dispatch_buffer = m;
639 } // namespace lyx