Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
33093de
Start implementing custom code templates
travkin79 Jan 9, 2026
2bc3cde
Use TM4E grammar and reconsiler for code template syntax highlighting
travkin79 Jan 9, 2026
1710b9f
Temporarily switch off null-safety check for a method
travkin79 Jan 13, 2026
ea5ec10
Rename classes Tm4e* to Tm*, remove "(TM4E)" in names
travkin79 Jan 13, 2026
8d9391a
Rename Tm* classes to TM* classes
travkin79 Jan 13, 2026
9c84493
Update TODO comments, remove obsolete code
travkin79 Jan 13, 2026
1ef2605
Add prefs constant for "use code formatter" for code templates
travkin79 Jan 13, 2026
7d77925
Update TODO: configuring source viewer in the edit code template dialog
travkin79 Jan 13, 2026
8d8a255
Switch off deprecation warning for an unmodifiable method parameter
travkin79 Jan 13, 2026
79fd590
Export new package to other plug-ins
travkin79 Jan 13, 2026
a6abd34
Extract calculating context type ID to a separate class
travkin79 Jan 13, 2026
1d1d8ac
Extract another convenience method
travkin79 Jan 13, 2026
9d64290
Extract additional convenience method for template context types
travkin79 Jan 23, 2026
72cee95
Re-word TODO comment
travkin79 Jan 23, 2026
8585c94
Add a TODO comment on adding code template variable resolvers
travkin79 Jan 23, 2026
91eaeda
Set min size for name text field in edit code template dialog
travkin79 Jan 28, 2026
f9dac23
Remove "Use code formatter" checkbox from templates preferences page
travkin79 Jan 28, 2026
96c03fb
Add user documentation
travkin79 Jan 28, 2026
b1b1917
Add syntax highlighting to edit code template dialog
travkin79 Jan 29, 2026
36844a9
Highlight code template variables in preferences
travkin79 Jan 29, 2026
feeaf09
Remove unnecessary type check
travkin79 Jan 29, 2026
18f18de
Fix compile error when built by Maven
travkin79 Jan 30, 2026
3b7834e
Remove unnecessary @Nullable annotations and null checks
travkin79 Jan 30, 2026
d08a405
Avoid unnecessary calls of castNonNull(...)
travkin79 Jan 30, 2026
fd17d89
Simplify async method call
travkin79 Feb 2, 2026
456ea93
Use content type name instead of grammar name for unique context type
travkin79 Feb 2, 2026
95b4297
Remove unnecessary type check
travkin79 Feb 2, 2026
252ace7
Extract method
travkin79 Feb 2, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added docs/img/code_template_proposal.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/img/templates_preferences.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
24 changes: 23 additions & 1 deletion docs/user-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,26 @@ Language-configuration files enable additional editor behavior for a language. T
These behaviors are applied on top of whatever the underlying editor already provides.
TM4E can add these behaviors to simple text editors, or refine them when an editor or language server already offers partial support.

### 3) Diagnostic tools (token hover)
### 3) Custom code templates and code proposals

TM4E offers support for defining and proposing custom code templates for the languages available through a TextMate grammar.
Users can specify their own code templates using the `TextMate > Templates` preferences page.

Each template is registered to a context type, i.e. a language or grammar (technically a TextMate scope).
In addition to available grammars, TM4E offers two special context types for comments.
Templates registered for these context types can be used in all languages having such comments in their grammar.

For example, users can register custom C/C++ or JavaScript statements, but also generally usable comment texts like a Copyright notice, a license, or a TODO comment with the user's name.
Having a TM4E-based editor open, they'll get that code snippets suggested via code completion triggered by Ctrl + Space.

The two generic context types for comments are:

- Comment (any comment that is not a documentation comment, i.e. line comments and standard block comments like code between `/*` and `*/` in Java / C++)
- Documentation comment (a comment that is used for code documentation, e.g. javadoc comments in Java, i.e. code between `/**` and `*/`)

![Custom Code Proposal](img/code_template_proposal.png)

### 4) Diagnostic tools (token hover)

For advanced users, some editors expose a TextMate token hover that shows the token scopes and partition information at the caret location.

Expand Down Expand Up @@ -102,6 +121,9 @@ Most user-facing configuration lives under the `TextMate` section in the Eclipse
1. `TextMate > Task Tags` lets you define tags in comments (such as `TODO` or `FIXME`) that should be treated as tasks or problems, and configure how they are marked in the workspace.\
![Task Tags Preferences](img/task_tags_preferences.png)

1. `TextMate > Templates` lets you specify custom code templates for available TextMate grammars (languages). These will be used in code proposals triggered by Ctrl + Space.\
![Templates Preferences](img/templates_preferences.png)

1. `TextMate > Themes` lets you choose between built-in Light and Dark themes and any additional themes contributed by installed plugins, as well as import extra theme files and set the default theme for light and dark modes.
You can also switch themes from the editor's context menu under `TextMate`.\
![Themes Preferences](img/themes_preferences.png)
Expand Down
1 change: 1 addition & 0 deletions org.eclipse.tm4e.ui/META-INF/MANIFEST.MF
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ Export-Package: org.eclipse.tm4e.ui,
org.eclipse.tm4e.ui.internal.widgets;x-friends:="org.eclipse.tm4e.languageconfiguration",
org.eclipse.tm4e.ui.model,
org.eclipse.tm4e.ui.samples,
org.eclipse.tm4e.ui.templates,
org.eclipse.tm4e.ui.text,
org.eclipse.tm4e.ui.themes,
org.eclipse.tm4e.ui.themes.css
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions org.eclipse.tm4e.ui/plugin.properties
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ TextMatePreferencePage.name=TextMate
GrammarPreferencePage.name=Grammar
TaskTagsPreferencePage.name=Task Tags
ThemePreferencePage.name=Theme
TemplatesPreferencePage.name=Templates

# Wizards
TextMateWizard.category=TextMate
Expand Down
40 changes: 40 additions & 0 deletions org.eclipse.tm4e.ui/plugin.xml
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,11 @@
class="org.eclipse.tm4e.ui.internal.preferences.ThemePreferencePage"
id="org.eclipse.tm4e.ui.preferences.ThemePreferencePage"
category="org.eclipse.tm4e.ui.preferences.TextMatePreferencePage" />
<page name="%TemplatesPreferencePage.name"
class="org.eclipse.tm4e.ui.internal.preferences.CustomCodeTemplatePreferencePage"
id="org.eclipse.tm4e.ui.preferences.CustomCodeTemplatePreferencePage"
category="org.eclipse.tm4e.ui.preferences.TextMatePreferencePage">
</page>
</extension>

<!-- Wizards -->
Expand Down Expand Up @@ -155,4 +160,39 @@
class="org.eclipse.tm4e.ui.internal.hover.TMTokenTextHover"
contentType="org.eclipse.core.runtime.text" />
</extension>

<extension point="org.eclipse.ui.editors.templates">
<contextType
class="org.eclipse.tm4e.ui.templates.DefaultTMTemplateContextType"
id="org.eclipse.tm4e.ui.templates.context"
name="Default context"
registryId="org.eclipse.tm4e.ui.templates">
</contextType>
<contextType
class="org.eclipse.tm4e.ui.templates.CommentTemplateContextType"
id="org.eclipse.tm4e.ui.templates.context.comment"
name="Comment"
registryId="org.eclipse.tm4e.ui.templates">
</contextType>
<contextType
class="org.eclipse.tm4e.ui.templates.DocumentationCommentTemplateContextType"
id="org.eclipse.tm4e.ui.templates.context.comment.doc"
name="Documentation Comment"
registryId="org.eclipse.tm4e.ui.templates">
</contextType>
<include
file="src/main/resources/templates/templates.xml"
translations="src/main/resources/templates/templates.properties">
</include>
<contextTypeRegistry
id="org.eclipse.tm4e.ui.templates">
</contextTypeRegistry>
</extension>

<extension point="org.eclipse.ui.genericeditor.contentAssistProcessors">
<contentAssistProcessor
class="org.eclipse.tm4e.ui.internal.templates.TMTemplateCompletionProcessor"
contentType="org.eclipse.core.runtime.text" />
</extension>

</plugin>
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
/*******************************************************************************
* Copyright (c) 2026 Advantest Europe GmbH and others.
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Dietrich Travkin (SOLUNAR GmbH) - initial implementation
*******************************************************************************/
package org.eclipse.tm4e.ui;

import java.net.URL;

import org.eclipse.core.runtime.FileLocator;
import org.eclipse.core.runtime.Path;
import org.eclipse.core.runtime.Platform;
import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.jface.resource.ImageRegistry;
import org.eclipse.swt.graphics.Image;
import org.osgi.framework.Bundle;

public final class TMImages {

private static final String ICONS_PATH = "$nl$/icons/full/"; //$NON-NLS-1$
private static final String OBJECT = ICONS_PATH + "obj16/"; // basic colors - size 16x16 //$NON-NLS-1$

public static final String IMG_TEMPLATE = "IMG_TEMPALTE"; //$NON-NLS-1$

private TMImages() {
// no instantiation desired
}

private static @Nullable ImageRegistry imageRegistry;

public static void initalize(final ImageRegistry registry) {
imageRegistry = registry;

registerImage(IMG_TEMPLATE, OBJECT + "template_obj.svg"); //$NON-NLS-1$
}

private static void registerImage(final String key, final String path) {
ImageDescriptor desc = ImageDescriptor.getMissingImageDescriptor();
final Bundle bundle = Platform.getBundle(TMUIPlugin.PLUGIN_ID);
final ImageRegistry imageRegistry = getImageRegistry();
URL url = null;
if (bundle != null) {
url = FileLocator.find(bundle, new Path(path), null);
if (url != null) {
desc = ImageDescriptor.createFromURL(url);
}
}
if (imageRegistry != null) {
imageRegistry.put(key, desc);
}
}

/**
* Returns the {@link Image} identified by the given key, or <code>null</code> if it does not exist.
*/
public static @Nullable Image getImage(final String key) {
final ImageRegistry imageRegistry = getImageRegistry();
if (imageRegistry == null) {
return null;
}
return imageRegistry.get(key);
}

/**
* Returns the {@link ImageDescriptor} identified by the given key, or <code>null</code> if it does not exist.
*/
public static @Nullable ImageDescriptor getImageDescriptor(final String key) {
final ImageRegistry imageRegistry = getImageRegistry();
if (imageRegistry == null) {
return null;
}
return imageRegistry.getDescriptor(key);
}

public static @Nullable ImageRegistry getImageRegistry() {
if (imageRegistry == null) {
final TMUIPlugin plugin = TMUIPlugin.getDefault();
if (plugin == null) {
return null;
}
imageRegistry = plugin.getImageRegistry();
}
return imageRegistry;
}

}
112 changes: 112 additions & 0 deletions org.eclipse.tm4e.ui/src/main/java/org/eclipse/tm4e/ui/TMUIPlugin.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,12 @@
*
* Contributors:
* Angelo Zerr <angelo.zerr@gmail.com> - initial API and implementation
* Dietrich Travkin (SOLUNAR GmbH) - Additions for custom code templates
*/
package org.eclipse.tm4e.ui;

import java.io.IOException;
import java.util.Iterator;
import java.util.logging.Handler;
import java.util.logging.Level;
import java.util.logging.LogRecord;
Expand All @@ -20,13 +23,26 @@
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.Status;
import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.jface.text.templates.TemplateContextType;
import org.eclipse.jface.text.templates.persistence.TemplateStore;
import org.eclipse.text.templates.ContextTypeRegistry;
import org.eclipse.tm4e.registry.IGrammarDefinition;
import org.eclipse.tm4e.registry.ITMScope;
import org.eclipse.tm4e.registry.TMEclipseRegistryPlugin;
import org.eclipse.tm4e.ui.internal.model.TMModelManager;
import org.eclipse.tm4e.ui.internal.samples.SampleManager;
import org.eclipse.tm4e.ui.internal.themes.ThemeManager;
import org.eclipse.tm4e.ui.internal.utils.CodeTemplateContextTypeUtils;
import org.eclipse.tm4e.ui.model.ITMModelManager;
import org.eclipse.tm4e.ui.samples.ISampleManager;
import org.eclipse.tm4e.ui.templates.CommentTemplateContextType;
import org.eclipse.tm4e.ui.templates.DefaultTMTemplateContextType;
import org.eclipse.tm4e.ui.templates.DocumentationCommentTemplateContextType;
import org.eclipse.tm4e.ui.templates.TMLanguageTemplateContextType;
import org.eclipse.tm4e.ui.themes.ColorManager;
import org.eclipse.tm4e.ui.themes.IThemeManager;
import org.eclipse.ui.editors.text.templates.ContributionContextTypeRegistry;
import org.eclipse.ui.editors.text.templates.ContributionTemplateStore;
import org.eclipse.ui.plugin.AbstractUIPlugin;
import org.osgi.framework.BundleContext;

Expand All @@ -39,9 +55,17 @@ public class TMUIPlugin extends AbstractUIPlugin {
public static final String PLUGIN_ID = "org.eclipse.tm4e.ui"; //$NON-NLS-1$
private static final String TRACE_ID = PLUGIN_ID + "/trace"; //$NON-NLS-1$

// IDs for custom code templates
private static final String CUSTOM_TEMPLATES_KEY = PLUGIN_ID + ".text.templates.custom"; //$NON-NLS-1$
private static final String TEMPLATES_REGISTRY_ID = PLUGIN_ID + ".templates"; //$NON-NLS-1$

// The shared instance
private static volatile @Nullable TMUIPlugin plugin;

// registry and store for custom code templates
private @Nullable ContributionContextTypeRegistry contextTypeRegistry = null;
private @Nullable TemplateStore templateStore = null;

/**
* Returns the shared instance
*
Expand Down Expand Up @@ -146,12 +170,100 @@ public void close() throws SecurityException {
}
});
}

TMImages.initalize(getImageRegistry());
}

@Override
public void stop(final BundleContext context) throws Exception {
if (templateStore != null) {
templateStore.stopListeningForPreferenceChanges();
}
ColorManager.getInstance().dispose();
plugin = null;
super.stop(context);
}

public ContextTypeRegistry getTemplateContextRegistry() {
final var contextTypeRegistry = this.contextTypeRegistry;
if (contextTypeRegistry != null) {
return contextTypeRegistry;
}

final var newContextTypeRegistry = new ContributionContextTypeRegistry(TEMPLATES_REGISTRY_ID);
this.contextTypeRegistry = newContextTypeRegistry;

newContextTypeRegistry.addContextType(DefaultTMTemplateContextType.CONTEXT_ID);
newContextTypeRegistry.addContextType(CommentTemplateContextType.CONTEXT_ID);
newContextTypeRegistry.addContextType(DocumentationCommentTemplateContextType.CONTEXT_ID);

// Add language-specific context types
// TODO Skip certain grammars? Some grammars have no name or are only used for highlighting code snippets, e.g. in Markdown
final IGrammarDefinition[] grammarDefinitions = TMEclipseRegistryPlugin.getGrammarRegistryManager().getDefinitions();
for (final IGrammarDefinition definition : grammarDefinitions) {
final ITMScope languageScope = definition.getScope();

// TODO It seems TemplatePreferencePage.EditTemplateDialog requires the context type names to be unique. Can we shorten the names somehow?
final String contextTypeName = CodeTemplateContextTypeUtils.toContextTypeName(languageScope);

final TMLanguageTemplateContextType languageContextType = new TMLanguageTemplateContextType(
contextTypeName, languageScope);
newContextTypeRegistry.addContextType(languageContextType);
}

return newContextTypeRegistry;
}

@SuppressWarnings("deprecation")
private static class ContextTypeRegistryWrapper extends org.eclipse.jface.text.templates.ContextTypeRegistry {

private final ContextTypeRegistry delegate;

public ContextTypeRegistryWrapper(final ContextTypeRegistry registry) {
this.delegate = registry;
}

@Override
public Iterator<TemplateContextType> contextTypes() {
return delegate.contextTypes();
}

@Override
public void addContextType(final TemplateContextType contextType) {
delegate.addContextType(contextType);
}

@Override
public @Nullable TemplateContextType getContextType(final String id) {
return delegate.getContextType(id);
}

}

public static ContextTypeRegistryWrapper from(final ContextTypeRegistry registry) {
return new ContextTypeRegistryWrapper(registry);
}

public TemplateStore getTemplateStore() {
final var templateStore = this.templateStore;

if (templateStore != null) {
return templateStore;
}

final TemplateStore newTemplateStore = new ContributionTemplateStore(
from(getTemplateContextRegistry()), getPreferenceStore(), CUSTOM_TEMPLATES_KEY);
this.templateStore = newTemplateStore;

try {
newTemplateStore.load();
} catch (final IOException e) {
Platform.getLog(this.getClass()).error(e.getMessage(), e);
}

newTemplateStore.startListeningForPreferenceChanges();

return newTemplateStore;
}

}
Loading
Loading