Skip to main content
aboutsummaryrefslogtreecommitdiffstats
blob: 2775269b9b01831e16eb952c4cd31eb38e89a52e (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
/*****************************************************************************
 * Copyright (c) 2021-2022 CEA LIST and others.
 *
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License 2.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * Contributors:
 *  Vincent Lorenzo (CEA LIST) <vincent.lorenzo@cea.fr> - Initial API and implementation
 *  Vincent Lorenzo (CEA LIST) <vincent.lorenzo@cea.fr> - Bug 578648, 579033, 580115, 580042
 *****************************************************************************/

package org.eclipse.papyrus.infra.textedit.xtext.nested.editor;

import java.io.IOException;
import java.util.EventObject;

import org.eclipse.core.commands.operations.IOperationHistory;
import org.eclipse.core.commands.operations.OperationHistoryFactory;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.emf.common.command.Command;
import org.eclipse.emf.common.command.CommandStack;
import org.eclipse.emf.common.command.CommandStackListener;
import org.eclipse.emf.common.notify.Adapter;
import org.eclipse.emf.common.notify.impl.AdapterImpl;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.transaction.TransactionalEditingDomain;
import org.eclipse.jface.text.IDocument;
import org.eclipse.osgi.util.NLS;
import org.eclipse.papyrus.infra.core.resource.ModelSet;
import org.eclipse.papyrus.infra.core.sasheditor.editor.ISashWindowsContainer;
import org.eclipse.papyrus.infra.core.services.ServiceException;
import org.eclipse.papyrus.infra.core.services.ServicesRegistry;
import org.eclipse.papyrus.infra.core.utils.ServiceUtils;
import org.eclipse.papyrus.infra.emf.gmf.command.NotifyingWorkspaceCommandStack;
import org.eclipse.papyrus.infra.gmfdiag.extensionpoints.editors.configuration.ICustomDirectEditorConfiguration;
import org.eclipse.papyrus.infra.textedit.textdocument.TextDocument;
import org.eclipse.papyrus.infra.textedit.textdocument.TextDocumentPackage;
import org.eclipse.papyrus.infra.textedit.xtext.Activator;
import org.eclipse.papyrus.infra.textedit.xtext.custom.PapyrusXTextDocumentProvider;
import org.eclipse.papyrus.infra.textedit.xtext.internal.command.TextUndoRedoCommandWrapper;
import org.eclipse.papyrus.infra.textedit.xtext.internal.listeners.SaveTextOnFocusLostPageLifeCycleEventsListener;
import org.eclipse.papyrus.infra.textedit.xtext.internal.listeners.SaveTextOnFocusLostPartListener;
import org.eclipse.papyrus.infra.textedit.xtext.internal.listeners.UndoableTextChangeListener;
import org.eclipse.papyrus.infra.ui.lifecycleevents.ISaveAndDirtyService;
import org.eclipse.ui.IEditorInput;
import org.eclipse.ui.IEditorSite;
import org.eclipse.ui.IPartListener2;
import org.eclipse.ui.IWorkbenchPage;
import org.eclipse.ui.PartInitException;
import org.eclipse.ui.texteditor.IDocumentProvider;
import org.eclipse.xtext.ui.editor.XtextEditor;
import org.eclipse.xtext.ui.editor.model.IXtextDocument;
import org.eclipse.xtext.ui.editor.model.XtextDocument;
import org.eclipse.xtext.ui.editor.model.XtextDocumentProvider;


/**
 * This Customization of the {@link XtextEditor} allows us to open an XtextEditor in the Papyrus sash editor
 */
public class PapyrusXTextEditor extends XtextEditor {

	/**
	 * the listener on the command stack
	 */
	private CommandStackListener commandStackListener;

	/**
	 * a listener on the semanticContext of the {@link TextDocument}
	 */
	private Adapter textDocumentListener;

	/**
	 * the part listener. This listener is used to be able to save the editor contents on the focus lost
	 */
	private IPartListener2 partListener;

	/** the service registry */
	protected ServicesRegistry registry;

	/** the Papyrus TextDocument */
	protected TextDocument textDocument;

	/** the editing domain */
	protected TransactionalEditingDomain domain;

	/**
	 * the ModelSet
	 */
	protected ModelSet modelSet;

	/**
	 * the Papyrus save and dirty service
	 */
	protected ISaveAndDirtyService saveAndDirtyService;

	/**
	 * the Xtext editor configuration
	 */
	protected ICustomDirectEditorConfiguration editorConfiguration;

	/**
	 * the file extension of the Xtext file for the current grammar
	 */
	private String fileExtension;

	/**
	 * the storage used by this editor
	 */
	private NestedXTextEditorStorage storage = null;

	/**
	 * the editor input used for this editor
	 */
	private NestedXTextEditorInput input = null;

	/**
	 * The Papyrus {@link ISashWindowsContainer}
	 */
	private ISashWindowsContainer sashWindowsContainer = null;

	/**
	 * The listener used on the {@link ISashWindowsContainer}
	 */
	private SaveTextOnFocusLostPageLifeCycleEventsListener sashWindowsContainerListener = null;

	/**
	 * listener on the IOperationHistory
	 */
	private UndoableTextChangeListener historyListener;

	/**
	 *
	 * Constructor.
	 *
	 */
	public PapyrusXTextEditor() {
		super();
	}

	/**
	 *
	 * @return
	 *         the created part listener
	 */
	protected IPartListener2 createPartListener() {
		return new SaveTextOnFocusLostPartListener(this);
	}

	/**
	 *
	 * @param servicesRegistry
	 *            the Papyrus service registry
	 * @param textDocument
	 *            the edited text document
	 * @param editorConfiguration
	 *            the editor configuration
	 * @param fileExtension
	 *            the file extension to use for the current grammar
	 */
	public void configureXTextEditor(final ServicesRegistry servicesRegistry, final TextDocument textDocument, final ICustomDirectEditorConfiguration editorConfiguration, final String fileExtension) {
		this.registry = servicesRegistry;
		this.textDocument = textDocument;
		this.editorConfiguration = editorConfiguration;
		this.fileExtension = fileExtension;
		try {
			this.modelSet = this.registry.getService(ModelSet.class);
		} catch (ServiceException e1) {
			Activator.log.error("We can't find the ModelSet", e1); //$NON-NLS-1$
		}

		try {
			this.saveAndDirtyService = registry.getService(ISaveAndDirtyService.class);
		} catch (ServiceException e) {
			Activator.log.error("We can find the ISaveAndDirtyService", e); //$NON-NLS-1$
		}
		this.partListener = createPartListener();
		this.storage = new NestedXTextEditorStorage(this.editorConfiguration, this.textDocument, this.fileExtension);
		this.input = new NestedXTextEditorInput(this.storage, this.textDocument, this.editorConfiguration);
	}

	/**
	 * @see org.eclipse.xtext.ui.editor.XtextEditor#doSave(org.eclipse.core.runtime.IProgressMonitor)
	 *
	 * @param progressMonitor
	 */
	@Override
	public void doSave(IProgressMonitor progressMonitor) {
		// called by ISaveAndDirtyService
		// allow to execute PapyrusXTextDocumentProvider#doSaveDocument
		super.doSave(progressMonitor);
		// so then we need to recall modelSet.save
		try {
			// required to be sure the edition done by the command run by PapyrusXTextDocumentProvider will be also saved
			this.modelSet.save(progressMonitor);
		} catch (IOException e) {
			Activator.log.error("We can't save the modelSet", e); //$NON-NLS-1$
		}
	}


	/**
	 * @see org.eclipse.xtext.ui.editor.XtextEditor#getAdapter(java.lang.Class)
	 *
	 * @param <T>
	 * @param adapter
	 * @return
	 */
	@Override
	public <T> T getAdapter(Class<T> adapter) {
		if (adapter == EObject.class) {
			return adapter.cast(this.textDocument);
		}
		if (adapter == TextDocument.class) {
			return adapter.cast(this.textDocument);
		}
		return super.getAdapter(adapter);
	}


	/**
	 * This method is called to re-set the editor input
	 */
	private void updateEditorContent() {
		final PapyrusXTextDocumentProvider provider = getDocumentProvider();
		final IXtextDocument document = provider.getDocument(this.input);

		// we need to disable the listener in order to not add a new command into the commandstack!
		this.historyListener.disable();
		provider.updateTextEditorContent(this.input, document);

		this.historyListener.enable();
	}

	/**
	 * This method allows to save the typed text in the edited model
	 */
	public void saveTextInEditedModel() {
		performSave(true, new NullProgressMonitor());
	}

	/**
	 * this method registers the listeners
	 */
	protected void registerListeners() {
		this.commandStackListener = createCommandStackListener();
		if (this.commandStackListener != null) {
			this.domain.getCommandStack().addCommandStackListener(this.commandStackListener);
		}
		this.textDocumentListener = createTextDocumentListener();
		if (this.textDocumentListener != null) {
			this.textDocument.eAdapters().add(this.textDocumentListener);
		}
		if (this.saveAndDirtyService != null) {
			this.saveAndDirtyService.registerIsaveablePart(this);
		}
		final IWorkbenchPage page = getEditorSite().getPage();
		page.addPartListener(this.partListener);
		this.historyListener = new UndoableTextChangeListener(this.domain, getDocumentProvider().getDocument(this.input));
		getOperationHistory().addOperationHistoryListener(this.historyListener);

	}

	/**
	 * This method unregisters the listeners
	 */
	protected void unregisterListeners() {
		unregisterSashWindowsContainerListener();
		getOperationHistory().removeOperationHistoryListener(this.historyListener);
		final IWorkbenchPage page = getEditorSite().getPage();
		page.removePartListener(this.partListener);
		if (this.commandStackListener != null) {
			this.domain.getCommandStack().removeCommandStackListener(this.commandStackListener);
		}
		if (this.textDocumentListener != null) {
			this.textDocument.eAdapters().remove(this.textDocumentListener);
		}
		if (this.saveAndDirtyService != null) {
			this.saveAndDirtyService.removeIsaveablePart(this);
		}
	}


	/**
	 * @see org.eclipse.xtext.ui.editor.XtextEditor#init(org.eclipse.ui.IEditorSite, org.eclipse.ui.IEditorInput)
	 *
	 * @param site
	 * @param input
	 * @throws PartInitException
	 */
	@Override
	public void init(IEditorSite site, IEditorInput input) throws PartInitException {
		org.eclipse.core.runtime.Assert.isNotNull(this.registry, "The service registry must be set calling the method configureXTextEditor"); //$NON-NLS-1$

		try {
			domain = ServiceUtils.getInstance().getTransactionalEditingDomain(this.registry);
		} catch (ServiceException e) {
			Activator.log.error("We can't find the editing domain", e); //$NON-NLS-1$
		}
		super.init(site, input);
		registerListeners();
	}

	/**
	 * @see org.eclipse.xtext.ui.editor.XtextEditor#dispose()
	 *
	 */
	@Override
	public void dispose() {
		unregisterListeners();

		// the part listener is not notified on editor close
		saveTextInEditedModel();

		super.dispose();
	}

	/**
	 * @see org.eclipse.xtext.ui.editor.XtextEditor#doSetInput(org.eclipse.ui.IEditorInput)
	 *
	 * @param input
	 * @throws CoreException
	 */
	@Override
	protected void doSetInput(IEditorInput input) throws CoreException {
		if (this.textDocument != null
				&& this.textDocument.eResource() != null) {// resource can be null when we are destroying the TextDocument
			super.doSetInput(this.input);
			if (this.historyListener != null) {
				this.historyListener.updateXTextDocument(getDocumentProvider().getDocument(this.input));
			}

			final IDocumentProvider provider = getDocumentProvider();
			if (provider instanceof XtextDocumentProvider) {
				final XtextDocumentProvider xtextDocumentProvider = (XtextDocumentProvider) provider;
				final IDocument tmp = xtextDocumentProvider.getDocument(this.input);

				if (tmp instanceof XtextDocument) {
					final XtextDocument xtextDoc = (XtextDocument) tmp;
					// here to create and display validation marker just after the opening of the editor
					// bug 580042
					xtextDoc.checkAndUpdateAnnotations();
				}
			}
		}
	}


	/**
	 * @see org.eclipse.ui.texteditor.StatusTextEditor#setFocus()
	 *      Set the focus and add a listener on {@link ISashWindowsContainer} on the first call
	 */
	@Override
	public void setFocus() {
		registerSashWindowsContainerListener();
		super.setFocus();
	}

	/**
	 * This method register a listener on the {@link ISashWindowsContainer}, in order to be notified
	 * when the current editor doesn't have the focus anymore, to be able to save its contents
	 * We can't register this listener earlier, because when we reopen a Papyrus model where the XtextEditor is open,
	 * the {@link ISashWindowsContainer} is registered in the {@link ServicesRegistry} after the init!!!
	 */
	protected final void registerSashWindowsContainerListener() {
		if (this.sashWindowsContainer == null) {
			try {
				this.sashWindowsContainer = this.registry.getService(ISashWindowsContainer.class);
				this.sashWindowsContainerListener = new SaveTextOnFocusLostPageLifeCycleEventsListener(this);
				this.sashWindowsContainer.addPageChangedListener(this.sashWindowsContainerListener);
			} catch (ServiceException e) {
				// we get an exception when we reopen a Papyrus model with an XtextEditor already open.
				// This method is called at least 3 times during the loading and at the end we succeed to register the listen
				// see bug 578648
				Activator.log.warn(NLS.bind("The service {0} is not yet initialized. We will retry at the next focus change.", ISashWindowsContainer.class)); //$NON-NLS-1$
			}
		}
	}

	/**
	 * Remove the listener on the {@link ISashWindowsContainer}
	 */
	protected final void unregisterSashWindowsContainerListener() {
		if (this.sashWindowsContainer == null && this.sashWindowsContainerListener != null) {
			this.sashWindowsContainer.removePageChangedListener(this.sashWindowsContainerListener);
		}
		this.sashWindowsContainer = null;
		this.sashWindowsContainerListener = null;
	}

	/**
	 * this method do nothing to avoid to break Undo/Redo Papyrus nested editor (diagram/table/...)
	 * see Bug 579033: [Diagram][KeyBinding] Undo/Redo actions are broken in diagram and it seems comes form the keybinding
	 */
	@Override
	protected void createUndoRedoActions() {
		// do nothing to preserve Papyrus Undo/Redo feature/keybinding
	}

	/**
	 *
	 * @return
	 *         the {@link IOperationHistory}
	 */
	private IOperationHistory getOperationHistory() {
		return OperationHistoryFactory.getOperationHistory();
	}

	/**
	 * @see org.eclipse.ui.texteditor.AbstractDecoratedTextEditor#getDocumentProvider()
	 *
	 * @return
	 * @since 1.1
	 */
	@Override
	public PapyrusXTextDocumentProvider getDocumentProvider() {
		return (PapyrusXTextDocumentProvider) super.getDocumentProvider();
	}

	/**
	 *
	 * @return
	 *         the listener for the {@link CommandStack}, can be <code>null</code>
	 * @since 1.1
	 */
	protected CommandStackListener createCommandStackListener() {
		return new CommandStackListener() {

			@Override
			public void commandStackChanged(EventObject event) {
				final Object source = event.getSource();
				if (source instanceof NotifyingWorkspaceCommandStack) {
					NotifyingWorkspaceCommandStack stack = (NotifyingWorkspaceCommandStack) source;
					final Command cmd = stack.getMostRecentCommand();

					if (cmd instanceof TextUndoRedoCommandWrapper) {
						// there is nothing to do in this case, because this notification has been sent by ourself!
						return;
					}
				}
				updateEditorContent();
			}
		};
	}

	/**
	 *
	 * @return
	 *         the listener for {@link TextDocument}, can be <code>null</code>
	 * @since 1.1
	 */
	protected Adapter createTextDocumentListener() {
		return new AdapterImpl() {

			@Override
			public void notifyChanged(org.eclipse.emf.common.notify.Notification msg) {
				if (TextDocumentPackage.eINSTANCE.getTextDocument_SemanticContext().equals(msg.getFeature())) {
					updateEditorContent();
				}
			};
		};
	}
}

Back to the top