cb3589fa9d9919b301fdb294bc45b9f35b0a5f4c
[public/netxms.git] / src / java / netxms-eclipse / NXSL / src / org / netxms / ui / eclipse / nxsl / views / ScriptEditorView.java
1 /**
2 * NetXMS - open source network management system
3 * Copyright (C) 2003-2015 Victor Kirhenshtein
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
18 */
19 package org.netxms.ui.eclipse.nxsl.views;
20
21 import java.io.IOException;
22 import java.io.InputStream;
23 import java.util.PropertyResourceBundle;
24 import java.util.ResourceBundle;
25 import org.eclipse.core.runtime.IProgressMonitor;
26 import org.eclipse.jface.action.Action;
27 import org.eclipse.jface.action.IMenuListener;
28 import org.eclipse.jface.action.IMenuManager;
29 import org.eclipse.jface.action.IToolBarManager;
30 import org.eclipse.jface.action.MenuManager;
31 import org.eclipse.jface.action.Separator;
32 import org.eclipse.jface.dialogs.IDialogSettings;
33 import org.eclipse.jface.dialogs.IInputValidator;
34 import org.eclipse.jface.dialogs.InputDialog;
35 import org.eclipse.jface.text.IFindReplaceTarget;
36 import org.eclipse.jface.window.Window;
37 import org.eclipse.swt.SWT;
38 import org.eclipse.swt.custom.StyledText;
39 import org.eclipse.swt.dnd.Clipboard;
40 import org.eclipse.swt.dnd.TextTransfer;
41 import org.eclipse.swt.dnd.TransferData;
42 import org.eclipse.swt.events.ModifyEvent;
43 import org.eclipse.swt.events.ModifyListener;
44 import org.eclipse.swt.events.SelectionEvent;
45 import org.eclipse.swt.events.SelectionListener;
46 import org.eclipse.swt.graphics.Color;
47 import org.eclipse.swt.layout.FillLayout;
48 import org.eclipse.swt.widgets.Composite;
49 import org.eclipse.swt.widgets.Display;
50 import org.eclipse.swt.widgets.Menu;
51 import org.eclipse.ui.IActionBars;
52 import org.eclipse.ui.ISaveablePart;
53 import org.eclipse.ui.IViewSite;
54 import org.eclipse.ui.PartInitException;
55 import org.eclipse.ui.commands.ActionHandler;
56 import org.eclipse.ui.contexts.IContextService;
57 import org.eclipse.ui.handlers.IHandlerService;
58 import org.eclipse.ui.part.ViewPart;
59 import org.eclipse.ui.texteditor.FindReplaceAction;
60 import org.netxms.client.NXCSession;
61 import org.netxms.client.Script;
62 import org.netxms.client.ScriptCompilationResult;
63 import org.netxms.ui.eclipse.actions.RefreshAction;
64 import org.netxms.ui.eclipse.console.resources.SharedIcons;
65 import org.netxms.ui.eclipse.jobs.ConsoleJob;
66 import org.netxms.ui.eclipse.nxsl.Activator;
67 import org.netxms.ui.eclipse.nxsl.Messages;
68 import org.netxms.ui.eclipse.nxsl.widgets.ScriptEditor;
69 import org.netxms.ui.eclipse.shared.ConsoleSharedData;
70 import org.netxms.ui.eclipse.tools.MessageDialogHelper;
71 import org.netxms.ui.eclipse.tools.NXFindAndReplaceAction;
72 import org.netxms.ui.eclipse.widgets.CompositeWithMessageBar;
73
74 /**
75 * Script editor view
76 */
77 @SuppressWarnings("deprecation")
78 public class ScriptEditorView extends ViewPart implements ISaveablePart
79 {
80 public static final String ID = "org.netxms.ui.eclipse.nxsl.views.ScriptEditorView"; //$NON-NLS-1$
81
82 private static final Color ERROR_COLOR = new Color(Display.getDefault(), 255, 0, 0);
83
84 private NXCSession session;
85 private CompositeWithMessageBar editorMessageBar;
86 private ScriptEditor editor;
87 private long scriptId;
88 private String scriptName;
89 private Action actionRefresh;
90 private Action actionSave;
91 private Action actionCompile;
92 private Action actionShowLineNumbers;
93 private Action actionGoToLine;
94 private Action actionSelectAll;
95 private Action actionCut;
96 private Action actionCopy;
97 private Action actionPaste;
98 private FindReplaceAction actionFindReplace;
99 private boolean modified = false;
100 private boolean showLineNumbers = true;
101
102 /* (non-Javadoc)
103 * @see org.eclipse.ui.part.ViewPart#init(org.eclipse.ui.IViewSite)
104 */
105 @Override
106 public void init(IViewSite site) throws PartInitException
107 {
108 super.init(site);
109
110 session = ConsoleSharedData.getSession();
111 scriptId = Long.parseLong(site.getSecondaryId());
112
113 IDialogSettings settings = Activator.getDefault().getDialogSettings();
114 if (settings.get("ScriptEditor.showLineNumbers") != null)
115 showLineNumbers = settings.getBoolean("ScriptEditor.showLineNumbers");
116 }
117
118 /* (non-Javadoc)
119 * @see org.eclipse.ui.part.WorkbenchPart#createPartControl(org.eclipse.swt.widgets.Composite)
120 */
121 @Override
122 public void createPartControl(Composite parent)
123 {
124 parent.setLayout(new FillLayout());
125
126 editorMessageBar = new CompositeWithMessageBar(parent, SWT.NONE);
127 editor = new ScriptEditor(editorMessageBar, SWT.NONE, SWT.H_SCROLL | SWT.V_SCROLL, showLineNumbers);
128 editor.getTextWidget().addModifyListener(new ModifyListener() {
129 @Override
130 public void modifyText(ModifyEvent e)
131 {
132 if (!modified)
133 {
134 modified = true;
135 firePropertyChange(PROP_DIRTY);
136 actionSave.setEnabled(true);
137 actionFindReplace.update();
138
139 boolean selected = editor.getTextWidget().getSelectionCount() > 0;
140 actionCut.setEnabled(selected);
141 actionCopy.setEnabled(selected);
142 }
143 }
144 });
145 editor.getTextWidget().addSelectionListener(new SelectionListener() {
146 @Override
147 public void widgetSelected(SelectionEvent e)
148 {
149 boolean selected = editor.getTextWidget().getSelectionCount() > 0;
150 actionCut.setEnabled(selected);
151 actionCopy.setEnabled(selected);
152 }
153
154 @Override
155 public void widgetDefaultSelected(SelectionEvent e)
156 {
157 widgetSelected(e);
158 }
159 });
160 editorMessageBar.setContent(editor);
161
162 activateContext();
163 createActions();
164 contributeToActionBars();
165 createPopupMenu();
166
167 reloadScript();
168 }
169
170 /**
171 * Activate context
172 */
173 private void activateContext()
174 {
175 IContextService contextService = (IContextService)getSite().getService(IContextService.class);
176 if (contextService != null)
177 {
178 contextService.activateContext("org.netxms.ui.eclipse.nxsl.context.ScriptEditor"); //$NON-NLS-1$
179 }
180 }
181
182 /* (non-Javadoc)
183 * @see org.eclipse.ui.part.WorkbenchPart#setFocus()
184 */
185 @Override
186 public void setFocus()
187 {
188 editor.setFocus();
189 }
190
191 /**
192 * Create actions
193 */
194 private void createActions()
195 {
196 final IHandlerService handlerService = (IHandlerService)getSite().getService(IHandlerService.class);
197
198
199 actionFindReplace = NXFindAndReplaceAction.getFindReplaceAction(this);
200
201 actionRefresh = new RefreshAction(this) {
202 @Override
203 public void run()
204 {
205 reloadScript();
206 }
207 };
208
209 actionSave = new Action(Messages.get().ScriptEditorView_Save, SharedIcons.SAVE) {
210 @Override
211 public void run()
212 {
213 saveScript();
214 }
215 };
216 actionSave.setActionDefinitionId("org.netxms.ui.eclipse.nxsl.commands.save"); //$NON-NLS-1$
217 handlerService.activateHandler(actionSave.getActionDefinitionId(), new ActionHandler(actionSave));
218
219 actionCompile = new Action("&Compile", Activator.getImageDescriptor("icons/compile.gif")) {
220 @Override
221 public void run()
222 {
223 compileScript();
224 }
225 };
226 actionCompile.setActionDefinitionId("org.netxms.ui.eclipse.nxsl.commands.compile"); //$NON-NLS-1$
227 handlerService.activateHandler(actionCompile.getActionDefinitionId(), new ActionHandler(actionCompile));
228
229 actionShowLineNumbers = new Action("Show line &numbers", Action.AS_CHECK_BOX) {
230 @Override
231 public void run()
232 {
233 showLineNumbers = actionShowLineNumbers.isChecked();
234 editor.showLineNumbers(showLineNumbers);
235 }
236 };
237 actionShowLineNumbers.setChecked(showLineNumbers);
238 actionShowLineNumbers.setActionDefinitionId("org.netxms.ui.eclipse.nxsl.commands.showLineNumbers"); //$NON-NLS-1$
239 handlerService.activateHandler(actionShowLineNumbers.getActionDefinitionId(), new ActionHandler(actionShowLineNumbers));
240
241 actionGoToLine = new Action("&Go to line...") {
242 @Override
243 public void run()
244 {
245 goToLine();
246 }
247 };
248 actionGoToLine.setActionDefinitionId("org.netxms.ui.eclipse.nxsl.commands.goToLine"); //$NON-NLS-1$
249 handlerService.activateHandler(actionGoToLine.getActionDefinitionId(), new ActionHandler(actionGoToLine));
250
251 actionSelectAll = new Action("Select &all") {
252 @Override
253 public void run()
254 {
255 editor.getTextWidget().selectAll();
256 }
257 };
258 actionSelectAll.setActionDefinitionId("org.netxms.ui.eclipse.nxsl.commands.selectAll"); //$NON-NLS-1$
259 handlerService.activateHandler(actionSelectAll.getActionDefinitionId(), new ActionHandler(actionSelectAll));
260
261 actionCut = new Action("C&ut", SharedIcons.CUT) {
262 @Override
263 public void run()
264 {
265 editor.getTextWidget().cut();
266 }
267 };
268 actionCut.setActionDefinitionId("org.netxms.ui.eclipse.nxsl.commands.cut"); //$NON-NLS-1$
269 handlerService.activateHandler(actionCut.getActionDefinitionId(), new ActionHandler(actionCut));
270 actionCut.setEnabled(false);
271
272 actionCopy = new Action("&Copy", SharedIcons.COPY) {
273 @Override
274 public void run()
275 {
276 editor.getTextWidget().copy();
277 }
278 };
279 actionCopy.setActionDefinitionId("org.netxms.ui.eclipse.nxsl.commands.copy"); //$NON-NLS-1$
280 handlerService.activateHandler(actionCopy.getActionDefinitionId(), new ActionHandler(actionCopy));
281 actionCopy.setEnabled(false);
282
283 actionPaste = new Action("&Paste", SharedIcons.PASTE) {
284 @Override
285 public void run()
286 {
287 editor.getTextWidget().paste();
288 }
289 };
290 actionPaste.setActionDefinitionId("org.netxms.ui.eclipse.nxsl.commands.paste"); //$NON-NLS-1$
291 handlerService.activateHandler(actionPaste.getActionDefinitionId(), new ActionHandler(actionPaste));
292 actionPaste.setEnabled(canPaste());
293 }
294
295 /**
296 * Contribute actions to action bar
297 */
298 private void contributeToActionBars()
299 {
300 IActionBars bars = getViewSite().getActionBars();
301 fillLocalPullDown(bars.getMenuManager());
302 fillLocalToolBar(bars.getToolBarManager());
303 }
304
305 /**
306 * Fill local pull-down menu
307 *
308 * @param manager
309 * Menu manager for pull-down menu
310 */
311 private void fillLocalPullDown(IMenuManager manager)
312 {
313 manager.add(actionFindReplace);
314 manager.add(actionGoToLine);
315 manager.add(new Separator());
316 manager.add(actionCut);
317 manager.add(actionCopy);
318 manager.add(actionPaste);
319 manager.add(actionSelectAll);
320 manager.add(new Separator());
321 manager.add(actionShowLineNumbers);
322 manager.add(new Separator());
323 manager.add(actionCompile);
324 manager.add(actionSave);
325 manager.add(new Separator());
326 manager.add(actionRefresh);
327 }
328
329 /**
330 * Fill local tool bar
331 *
332 * @param manager
333 * Menu manager for local toolbar
334 */
335 private void fillLocalToolBar(IToolBarManager manager)
336 {
337 manager.add(actionCompile);
338 manager.add(actionSave);
339 manager.add(new Separator());
340 manager.add(actionRefresh);
341 }
342
343 /**
344 * Create pop-up menu for user list
345 */
346 private void createPopupMenu()
347 {
348 // Create menu manager
349 MenuManager menuMgr = new MenuManager();
350 menuMgr.setRemoveAllWhenShown(true);
351 menuMgr.addMenuListener(new IMenuListener() {
352 public void menuAboutToShow(IMenuManager manager)
353 {
354 fillContextMenu(manager);
355 }
356 });
357
358 // Create menu
359 Menu menu = menuMgr.createContextMenu(editor.getTextWidget());
360 editor.getTextWidget().setMenu(menu);
361 }
362
363 /**
364 * Fill context menu
365 *
366 * @param mgr Menu manager
367 */
368 protected void fillContextMenu(final IMenuManager manager)
369 {
370 actionPaste.setEnabled(canPaste());
371
372 manager.add(actionFindReplace);
373 manager.add(actionGoToLine);
374 manager.add(new Separator());
375 manager.add(actionCut);
376 manager.add(actionCopy);
377 manager.add(actionPaste);
378 manager.add(actionSelectAll);
379 manager.add(new Separator());
380 manager.add(actionShowLineNumbers);
381 manager.add(new Separator());
382 manager.add(actionCompile);
383 manager.add(actionSave);
384 }
385
386 /**
387 * Check if paste action can work
388 *
389 * @return
390 */
391 private boolean canPaste()
392 {
393 Clipboard cb = new Clipboard(Display.getCurrent());
394 TransferData[] available = cb.getAvailableTypes();
395 boolean enabled = false;
396 for(int i = 0; i < available.length; i++)
397 {
398 if (TextTransfer.getInstance().isSupportedType(available[i]))
399 {
400 enabled = true;
401 break;
402 }
403 }
404 cb.dispose();
405 return enabled;
406 }
407
408 /**
409 * Go to specific line
410 */
411 private void goToLine()
412 {
413 StyledText textControl = editor.getTextWidget();
414 final int maxLine = textControl.getLineCount();
415
416 InputDialog dlg = new InputDialog(getSite().getShell(), "Go to Line",
417 String.format("Enter line number (1..%d)", maxLine),
418 Integer.toString(textControl.getLineAtOffset(textControl.getCaretOffset()) + 1),
419 new IInputValidator() {
420 @Override
421 public String isValid(String newText)
422 {
423 try
424 {
425 int n = Integer.parseInt(newText);
426 if ((n < 1) || (n > maxLine))
427 return "Number out of range";
428 return null;
429 }
430 catch(NumberFormatException e)
431 {
432 return "Invalid number";
433 }
434 }
435 });
436 if (dlg.open() != Window.OK)
437 return;
438
439 int line = Integer.parseInt(dlg.getValue());
440 textControl.setCaretOffset(textControl.getOffsetAtLine(line - 1));
441 }
442
443 /**
444 * Reload script from server
445 */
446 private void reloadScript()
447 {
448 new ConsoleJob(String.format(Messages.get().ScriptEditorView_LoadJobTitle, scriptId), this, Activator.PLUGIN_ID, null) {
449 @Override
450 protected String getErrorMessage()
451 {
452 return String.format(Messages.get().ScriptEditorView_LoadJobError, scriptId);
453 }
454
455 @Override
456 protected void runInternal(IProgressMonitor monitor) throws Exception
457 {
458 final Script script = session.getScript(scriptId);
459 runInUIThread(new Runnable() {
460 @Override
461 public void run()
462 {
463 scriptName = script.getName();
464 setPartName(String.format(Messages.get().ScriptEditorView_PartName, scriptName));
465 editor.setText(script.getSource());
466 actionSave.setEnabled(false);
467 actionFindReplace.update();
468 modified = false;
469 firePropertyChange(PROP_DIRTY);
470 }
471 });
472 }
473 }.start();
474 }
475
476 /**
477 * Compile script
478 */
479 private void compileScript()
480 {
481 final String source = editor.getText();
482 editor.getTextWidget().setEditable(false);
483 new ConsoleJob("Compile script", this, Activator.PLUGIN_ID, null) {
484 @Override
485 protected String getErrorMessage()
486 {
487 return "Cannot compile script";
488 }
489
490 @Override
491 protected void runInternal(IProgressMonitor monitor) throws Exception
492 {
493 final ScriptCompilationResult result = session.compileScript(source, false);
494 runInUIThread(new Runnable() {
495 @Override
496 public void run()
497 {
498 StyledText s = editor.getTextWidget();
499 s.setLineBackground(0, s.getLineCount(), null);
500 if (result.success)
501 {
502 editorMessageBar.showMessage(CompositeWithMessageBar.INFORMATION, "Script compiled successfully");
503 }
504 else
505 {
506 editorMessageBar.showMessage(CompositeWithMessageBar.WARNING, result.errorMessage);
507 s.setLineBackground(result.errorLine - 1, 1, ERROR_COLOR);
508 }
509 editor.getTextWidget().setEditable(true);
510 }
511 });
512 }
513 }.start();
514 }
515
516 /**
517 * Save script
518 */
519 private void saveScript()
520 {
521 final String source = editor.getText();
522 editor.getTextWidget().setEditable(false);
523 new ConsoleJob(Messages.get().ScriptEditorView_SaveJobTitle, this, Activator.PLUGIN_ID, null) {
524 @Override
525 protected String getErrorMessage()
526 {
527 return Messages.get().ScriptEditorView_SaveJobError;
528 }
529
530 @Override
531 protected void runInternal(IProgressMonitor monitor) throws Exception
532 {
533 final ScriptCompilationResult result = session.compileScript(source, false);
534 if (result.success)
535 {
536 getDisplay().syncExec(new Runnable() {
537 @Override
538 public void run()
539 {
540 editorMessageBar.hideMessage();
541 StyledText s = editor.getTextWidget();
542 s.setLineBackground(0, s.getLineCount(), null);
543 }
544 });
545 }
546 else
547 {
548 getDisplay().syncExec(new Runnable() {
549 @Override
550 public void run()
551 {
552 if (MessageDialogHelper.openQuestion(getSite().getShell(), "Compilation Errors",
553 String.format("Script compilation failed (%s)\r\nSave changes anyway?", result.errorMessage)))
554 result.success = true;
555 editorMessageBar.showMessage(CompositeWithMessageBar.WARNING, result.errorMessage);
556 StyledText s = editor.getTextWidget();
557 s.setLineBackground(0, s.getLineCount(), null);
558 s.setLineBackground(result.errorLine - 1, 1, ERROR_COLOR);
559 }
560 });
561 }
562 if (result.success)
563 {
564 doScriptSave(source, monitor);
565 }
566 else
567 {
568 runInUIThread(new Runnable() {
569 @Override
570 public void run()
571 {
572 editor.getTextWidget().setEditable(true);
573 }
574 });
575 }
576 }
577 }.start();
578 }
579
580 /**
581 * Do actual script save
582 *
583 * @param source
584 * @param monitor
585 * @throws Exception
586 */
587 private void doScriptSave(String source, IProgressMonitor monitor) throws Exception
588 {
589 session.modifyScript(scriptId, scriptName, source);
590 editor.getDisplay().asyncExec(new Runnable() {
591 @Override
592 public void run()
593 {
594 if (editor.isDisposed())
595 return;
596
597 editor.getTextWidget().setEditable(true);
598 actionSave.setEnabled(false);
599 modified = false;
600 firePropertyChange(PROP_DIRTY);
601 }
602 });
603 }
604
605 /* (non-Javadoc)
606 * @see org.eclipse.ui.part.WorkbenchPart#getAdapter(java.lang.Class)
607 */
608 @SuppressWarnings("rawtypes")
609 @Override
610 public Object getAdapter(Class adapter)
611 {
612 Object object = super.getAdapter(adapter);
613 if (object != null)
614 {
615 return object;
616 }
617 if (adapter.equals(IFindReplaceTarget.class))
618 {
619 return editor.getFindReplaceTarget();
620 }
621 return null;
622 }
623
624 /* (non-Javadoc)
625 * @see org.eclipse.ui.ISaveablePart#doSave(org.eclipse.core.runtime.IProgressMonitor)
626 */
627 @Override
628 public void doSave(IProgressMonitor monitor)
629 {
630 final String source = editor.getText();
631 editor.getTextWidget().setEditable(false);
632 try
633 {
634 doScriptSave(source, monitor);
635 }
636 catch(Exception e)
637 {
638 MessageDialogHelper.openError(getViewSite().getShell(), Messages.get().ScriptEditorView_Error, String.format(Messages.get().ScriptEditorView_SaveErrorMessage, e.getMessage()));
639 }
640 }
641
642 /* (non-Javadoc)
643 * @see org.eclipse.ui.ISaveablePart#doSaveAs()
644 */
645 @Override
646 public void doSaveAs()
647 {
648 }
649
650 /* (non-Javadoc)
651 * @see org.eclipse.ui.ISaveablePart#isDirty()
652 */
653 @Override
654 public boolean isDirty()
655 {
656 return modified;
657 }
658
659 /* (non-Javadoc)
660 * @see org.eclipse.ui.ISaveablePart#isSaveAsAllowed()
661 */
662 @Override
663 public boolean isSaveAsAllowed()
664 {
665 return false;
666 }
667
668 /* (non-Javadoc)
669 * @see org.eclipse.ui.ISaveablePart#isSaveOnCloseNeeded()
670 */
671 @Override
672 public boolean isSaveOnCloseNeeded()
673 {
674 return modified;
675 }
676
677 /* (non-Javadoc)
678 * @see org.eclipse.ui.part.WorkbenchPart#dispose()
679 */
680 @Override
681 public void dispose()
682 {
683 IDialogSettings settings = Activator.getDefault().getDialogSettings();
684 settings.put("ScriptEditor.showLineNumbers", showLineNumbers);
685 super.dispose();
686 }
687 }