diff --git a/org.eclipse.tm4e.ui/META-INF/MANIFEST.MF b/org.eclipse.tm4e.ui/META-INF/MANIFEST.MF index c56333e22..7d9a297a5 100644 --- a/org.eclipse.tm4e.ui/META-INF/MANIFEST.MF +++ b/org.eclipse.tm4e.ui/META-INF/MANIFEST.MF @@ -4,7 +4,7 @@ Bundle-Name: %pluginName Bundle-Vendor: %providerName Bundle-Localization: plugin Bundle-SymbolicName: org.eclipse.tm4e.ui;singleton:=true -Bundle-Version: 0.6.2.qualifier +Bundle-Version: 0.6.3.qualifier Require-Bundle: org.eclipse.tm4e.core;bundle-version="0.5.2", org.eclipse.jface.text, org.eclipse.core.runtime, diff --git a/org.eclipse.tm4e.ui/plugin.properties b/org.eclipse.tm4e.ui/plugin.properties index 5a16eba0e..61557a201 100644 --- a/org.eclipse.tm4e.ui/plugin.properties +++ b/org.eclipse.tm4e.ui/plugin.properties @@ -25,6 +25,7 @@ Theme.Dark.name=Dark # Preferences TextMatePreferencePage.name=TextMate GrammarPreferencePage.name=Grammar +TaskTagsPreferencePage.name=Task Tags ThemePreferencePage.name=Theme # Wizards diff --git a/org.eclipse.tm4e.ui/plugin.xml b/org.eclipse.tm4e.ui/plugin.xml index 9bcf34b6e..fc8f1fb39 100644 --- a/org.eclipse.tm4e.ui/plugin.xml +++ b/org.eclipse.tm4e.ui/plugin.xml @@ -56,6 +56,10 @@ class="org.eclipse.tm4e.ui.internal.preferences.GrammarPreferencePage" id="org.eclipse.tm4e.ui.preferences.GrammarPreferencePage" category="org.eclipse.tm4e.ui.preferences.TextMatePreferencePage" /> + org.eclipse.tm4e.ui eclipse-plugin - 0.6.2-SNAPSHOT + 0.6.3-SNAPSHOT diff --git a/org.eclipse.tm4e.ui/src/main/java/org/eclipse/tm4e/ui/internal/TMUIMessages.java b/org.eclipse.tm4e.ui/src/main/java/org/eclipse/tm4e/ui/internal/TMUIMessages.java index 78684179a..bba374688 100644 --- a/org.eclipse.tm4e.ui/src/main/java/org/eclipse/tm4e/ui/internal/TMUIMessages.java +++ b/org.eclipse.tm4e.ui/src/main/java/org/eclipse/tm4e/ui/internal/TMUIMessages.java @@ -32,6 +32,7 @@ public final class TMUIMessages extends NLS { // TextMate preferences page public static String TextMatePreferencePage_GrammarRelatedLink; public static String TextMatePreferencePage_LanguageConfigurationRelatedLink; + public static String TextMatePreferencePage_TaskTagsRelatedLink; public static String TextMatePreferencePage_ThemeRelatedLink; // Grammar preferences page @@ -49,6 +50,18 @@ public final class TMUIMessages extends NLS { public static String GrammarPreferencePage_tab_injection_text; public static String GrammarPreferencePage_preview; + // Task Tags preferences page + public static String TaskTagsPreferencePage_description; + public static String TaskTagsPreferencePage_column_tag; + public static String TaskTagsPreferencePage_column_type; + public static String TaskTagsPreferencePage_column_level; + public static String TaskTagsPreferencePage_addTagDialog_windowTitle; + public static String TaskTagsPreferencePage_addTagDialog_header; + public static String TaskTagsPreferencePage_addTagDialog_message; + public static String TaskTagsPreferencePage_editTagDialog_windowTitle; + public static String TaskTagsPreferencePage_editTagDialog_header; + public static String TaskTagsPreferencePage_editTagDialog_message; + // Theme preferences page public static String ThemePreferencePage_title; public static String ThemePreferencePage_description; diff --git a/org.eclipse.tm4e.ui/src/main/java/org/eclipse/tm4e/ui/internal/TMUIMessages.properties b/org.eclipse.tm4e.ui/src/main/java/org/eclipse/tm4e/ui/internal/TMUIMessages.properties index 5ef9d1a10..538886b90 100644 --- a/org.eclipse.tm4e.ui/src/main/java/org/eclipse/tm4e/ui/internal/TMUIMessages.properties +++ b/org.eclipse.tm4e.ui/src/main/java/org/eclipse/tm4e/ui/internal/TMUIMessages.properties @@ -19,6 +19,7 @@ Button_browse_Workspace=Browse Workspace... # preference page TextMatePreferencePage_GrammarRelatedLink=See ''{0}'' for associating editors with grammars. TextMatePreferencePage_LanguageConfigurationRelatedLink=See ''{0}'' for associating editors with language configurations. +TextMatePreferencePage_TaskTagsRelatedLink=See ''{0}'' for task tags configuration. TextMatePreferencePage_ThemeRelatedLink=See ''{0}'' for associating editors with themes. GrammarPreferencePage_title=TextMate grammars @@ -33,6 +34,17 @@ GrammarPreferencePage_tab_theme_text=Theme GrammarPreferencePage_tab_injection_text=Injection GrammarPreferencePage_preview=Previe&w: +TaskTagsPreferencePage_description=Manage comment task tags: +TaskTagsPreferencePage_column_tag=Tag +TaskTagsPreferencePage_column_type=Marker Type +TaskTagsPreferencePage_column_level=Level +TaskTagsPreferencePage_addTagDialog_windowTitle=Task Tag +TaskTagsPreferencePage_addTagDialog_header=New task tag configuration +TaskTagsPreferencePage_addTagDialog_message=Create a new task tag configuration +TaskTagsPreferencePage_editTagDialog_windowTitle=Task Tag +TaskTagsPreferencePage_editTagDialog_header=Edit task tag configuration +TaskTagsPreferencePage_editTagDialog_message=Modify an existing task tag configuration + ThemePreferencePage_title=TextMate themes ThemePreferencePage_description=&Create, edit or remove TextMate themes: ThemePreferencePage_column_name=Name diff --git a/org.eclipse.tm4e.ui/src/main/java/org/eclipse/tm4e/ui/internal/preferences/PreferenceConstants.java b/org.eclipse.tm4e.ui/src/main/java/org/eclipse/tm4e/ui/internal/preferences/PreferenceConstants.java index 4e4cfb714..d14184dbc 100644 --- a/org.eclipse.tm4e.ui/src/main/java/org/eclipse/tm4e/ui/internal/preferences/PreferenceConstants.java +++ b/org.eclipse.tm4e.ui/src/main/java/org/eclipse/tm4e/ui/internal/preferences/PreferenceConstants.java @@ -17,6 +17,8 @@ */ public final class PreferenceConstants { + public static final String TASK_TAGS = "org.eclipse.tm4e.ui.taskTags"; + public static final String THEMES = "org.eclipse.tm4e.ui.themes"; public static final String THEME_ASSOCIATIONS = "org.eclipse.tm4e.ui.themeAssociations"; diff --git a/org.eclipse.tm4e.ui/src/main/java/org/eclipse/tm4e/ui/internal/preferences/PreferenceHelper.java b/org.eclipse.tm4e.ui/src/main/java/org/eclipse/tm4e/ui/internal/preferences/PreferenceHelper.java index 6e3663c1b..4a6edc056 100644 --- a/org.eclipse.tm4e.ui/src/main/java/org/eclipse/tm4e/ui/internal/preferences/PreferenceHelper.java +++ b/org.eclipse.tm4e.ui/src/main/java/org/eclipse/tm4e/ui/internal/preferences/PreferenceHelper.java @@ -11,26 +11,72 @@ */ package org.eclipse.tm4e.ui.internal.preferences; +import java.io.IOException; import java.util.Collection; +import java.util.Set; +import org.eclipse.core.runtime.preferences.InstanceScope; +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.eclipse.tm4e.ui.TMUIPlugin; +import org.eclipse.tm4e.ui.internal.utils.MarkerConfig; import org.eclipse.tm4e.ui.themes.IThemeAssociation; import org.eclipse.tm4e.ui.themes.ThemeAssociation; +import org.osgi.service.prefs.BackingStoreException; import com.google.gson.Gson; import com.google.gson.GsonBuilder; import com.google.gson.InstanceCreator; +import com.google.gson.JsonElement; +import com.google.gson.JsonSyntaxException; +import com.google.gson.TypeAdapter; +import com.google.gson.TypeAdapterFactory; +import com.google.gson.reflect.TypeToken; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonWriter; /** - * Helper class load, save theme preferences with Json format. - * + * Helper class to load, save preferences in JSON format. */ public final class PreferenceHelper { private static final Gson DEFAULT_GSON; static { - DEFAULT_GSON = new GsonBuilder().registerTypeAdapter(IThemeAssociation.class, - (InstanceCreator) type -> new ThemeAssociation()).create(); + DEFAULT_GSON = new GsonBuilder() // + .registerTypeAdapter(IThemeAssociation.class, (InstanceCreator) type -> new ThemeAssociation()) + .registerTypeAdapterFactory(new TypeAdapterFactory() { + @SuppressWarnings("unchecked") + @Override + @NonNullByDefault({}) + public @Nullable TypeAdapter create(final Gson gson, final TypeToken type) { + if (!MarkerConfig.class.isAssignableFrom(type.getRawType())) + return null; + + final var jsonElementAdapter = gson.getAdapter(JsonElement.class); + final var problemAdapter = gson.getDelegateAdapter(this, TypeToken.get(MarkerConfig.ProblemMarkerConfig.class)); + final var taskAdapter = gson.getDelegateAdapter(this, TypeToken.get(MarkerConfig.TaskMarkerConfig.class)); + return (TypeAdapter) new TypeAdapter() { + @Override + public void write(final JsonWriter out, final MarkerConfig value) throws IOException { + if (value.getClass().isAssignableFrom(MarkerConfig.ProblemMarkerConfig.class)) { + problemAdapter.write(out, (MarkerConfig.ProblemMarkerConfig) value); + } else if (value.getClass().isAssignableFrom(MarkerConfig.TaskMarkerConfig.class)) { + taskAdapter.write(out, (MarkerConfig.TaskMarkerConfig) value); + } + } + + @Override + public MarkerConfig read(final JsonReader in) throws IOException { + final var objectJson = jsonElementAdapter.read(in).getAsJsonObject(); + return switch (MarkerConfig.Type.valueOf(objectJson.get("type").getAsString())) { + case PROBLEM -> problemAdapter.fromJsonTree(objectJson); + case TASK -> taskAdapter.fromJsonTree(objectJson); + }; + } + }; + } + }).create(); } public static IThemeAssociation[] loadThemeAssociations(final String json) { @@ -41,6 +87,37 @@ public static String toJsonThemeAssociations(final Collection return DEFAULT_GSON.toJson(themeAssociations); } + public static Set loadMarkerConfigs() { + final var prefs = InstanceScope.INSTANCE.getNode(TMUIPlugin.PLUGIN_ID); + final var json = prefs.get(PreferenceConstants.TASK_TAGS, null); + Set result = null; + try { + result = loadMarkerConfigs(json); + } catch (JsonSyntaxException ex) { + TMUIPlugin.logError(ex); + } + return result == null ? MarkerConfig.getDefaults() : result; + } + + public static Set loadMarkerConfigs(final String json) { + return DEFAULT_GSON.fromJson(json, new TypeToken>() { + }.getType()); + } + + public static String toJsonMarkerConfigs(final Set markerConfigs) { + return DEFAULT_GSON.toJson(markerConfigs); + } + + public static void saveMarkerConfigs(final Set markerConfigs) { + final var prefs = InstanceScope.INSTANCE.getNode(TMUIPlugin.PLUGIN_ID); + prefs.put(PreferenceConstants.TASK_TAGS, toJsonMarkerConfigs(markerConfigs)); + try { + prefs.flush(); + } catch (final BackingStoreException ex) { + TMUIPlugin.logError(ex); + } + } + private PreferenceHelper() { } } diff --git a/org.eclipse.tm4e.ui/src/main/java/org/eclipse/tm4e/ui/internal/preferences/TaskTagsPreferencePage.java b/org.eclipse.tm4e.ui/src/main/java/org/eclipse/tm4e/ui/internal/preferences/TaskTagsPreferencePage.java new file mode 100644 index 000000000..3f0bd5ba4 --- /dev/null +++ b/org.eclipse.tm4e.ui/src/main/java/org/eclipse/tm4e/ui/internal/preferences/TaskTagsPreferencePage.java @@ -0,0 +1,339 @@ +/******************************************************************************* + * Copyright (c) 2023 Vegard IT GmbH and others. + * This program and the Faccompanying 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: + * Sebastian Thomschke (Vegard IT GmbH) - initial implementation + *******************************************************************************/ +package org.eclipse.tm4e.ui.internal.preferences; + +import static org.eclipse.tm4e.core.internal.utils.NullSafetyHelper.*; + +import java.util.Collection; +import java.util.Set; +import java.util.stream.Stream; + +import org.eclipse.jdt.annotation.Nullable; +import org.eclipse.jface.dialogs.IDialogConstants; +import org.eclipse.jface.dialogs.IMessageProvider; +import org.eclipse.jface.dialogs.TitleAreaDialog; +import org.eclipse.jface.preference.PreferencePage; +import org.eclipse.jface.resource.JFaceResources; +import org.eclipse.jface.viewers.IStructuredContentProvider; +import org.eclipse.jface.viewers.IStructuredSelection; +import org.eclipse.jface.viewers.ITableLabelProvider; +import org.eclipse.jface.viewers.LabelProvider; +import org.eclipse.jface.viewers.TableViewer; +import org.eclipse.jface.window.Window; +import org.eclipse.swt.SWT; +import org.eclipse.swt.graphics.GC; +import org.eclipse.swt.graphics.Image; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Button; +import org.eclipse.swt.widgets.Combo; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Event; +import org.eclipse.swt.widgets.Label; +import org.eclipse.swt.widgets.Shell; +import org.eclipse.swt.widgets.Table; +import org.eclipse.swt.widgets.TableColumn; +import org.eclipse.swt.widgets.Text; +import org.eclipse.tm4e.ui.internal.TMUIMessages; +import org.eclipse.tm4e.ui.internal.utils.MarkerConfig; +import org.eclipse.tm4e.ui.internal.utils.MarkerConfig.*; +import org.eclipse.tm4e.ui.internal.utils.MarkerUtils; +import org.eclipse.tm4e.ui.internal.widgets.ColumnSelectionAdapter; +import org.eclipse.tm4e.ui.internal.widgets.ColumnViewerComparator; +import org.eclipse.ui.IWorkbench; +import org.eclipse.ui.IWorkbenchPreferencePage; + +/** + * Task Tags preferences page. + */ +public final class TaskTagsPreferencePage extends PreferencePage implements IWorkbenchPreferencePage { + + static final String PAGE_ID = "org.eclipse.tm4e.ui.preferences.TaskTagsPreferencePage"; + + private static final class TableLabelProvider extends LabelProvider implements ITableLabelProvider { + @Override + public @Nullable Image getColumnImage(final @Nullable Object element, final int columnIndex) { + return null; + } + + @Override + public @Nullable String getText(final @Nullable Object element) { + return getColumnText(element, 0); + } + + @Override + public @Nullable String getColumnText(final @Nullable Object element, final int columnIndex) { + if (element == null) + return ""; + + final MarkerConfig item = (MarkerConfig) element; + return switch (columnIndex) { + case 0 -> item.tag; + case 1 -> item.type.name().charAt(0) + item.type.name().substring(1).toLowerCase(); + case 2 -> switch (item.type) { + case PROBLEM -> item.asProblemMarkerConfig().severity.toString(); + case TASK -> item.asTaskMarkerConfig().priority.toString(); + }; + default -> ""; + }; + } + } + + private final class MarkerConfigEditDialog extends TitleAreaDialog { + + @Nullable + MarkerConfig markerConfig; + + Text txtTag = lazyNonNull(); + Combo cmbType = lazyNonNull(); + Label lblLevel = lazyNonNull(); + Combo cmbLevel = lazyNonNull(); + + MarkerConfigEditDialog(final Shell parentShell, final @Nullable MarkerConfig markerConfig) { + super(parentShell); + this.markerConfig = markerConfig; + } + + @Override + public void create() { + super.create(); + if (markerConfig == null) { + getShell().setText(TMUIMessages.TaskTagsPreferencePage_addTagDialog_windowTitle); + setTitle(TMUIMessages.TaskTagsPreferencePage_addTagDialog_header); + setMessage(TMUIMessages.TaskTagsPreferencePage_addTagDialog_message, IMessageProvider.INFORMATION); + } else { + getShell().setText(TMUIMessages.TaskTagsPreferencePage_editTagDialog_windowTitle); + setTitle(TMUIMessages.TaskTagsPreferencePage_editTagDialog_header); + setMessage(TMUIMessages.TaskTagsPreferencePage_editTagDialog_message, IMessageProvider.INFORMATION); + } + validateInput(null); + } + + @Override + protected void okPressed() { + markerConfig = switch (Type.valueOf(cmbType.getText())) { + case PROBLEM -> new ProblemMarkerConfig(txtTag.getText(), ProblemSeverity.valueOf(cmbLevel.getText())); + case TASK -> new TaskMarkerConfig(txtTag.getText(), TaskPriority.valueOf(cmbLevel.getText())); + }; + super.okPressed(); + } + + void validateInput(@SuppressWarnings("unused") final @Nullable Event e) { + var btn = getButton(IDialogConstants.OK_ID); + if (btn == null) + return; + btn.setEnabled(!txtTag.getText().isBlank() && cmbType.getSelectionIndex() > -1 && cmbLevel.getSelectionIndex() > -1); + } + + @Override + protected Control createDialogArea(final @Nullable Composite parent) { + final var area = (Composite) super.createDialogArea(parent); + final var container = new Composite(area, SWT.NONE); + container.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); + final var layout = new GridLayout(2, false); + container.setLayout(layout); + createTagText(container); + createTypeCombo(container); + createLevel(container); + + final var markerConfig = this.markerConfig; + if (markerConfig != null) { + txtTag.setText(markerConfig.tag); + cmbType.setText(markerConfig.type.name()); + cmbLevel.setText(switch (markerConfig.type) { + case PROBLEM -> markerConfig.asProblemMarkerConfig().severity.name(); + case TASK -> markerConfig.asTaskMarkerConfig().priority.name(); + }); + } else { + cmbType.setText(MarkerConfig.Type.TASK.name()); + cmbLevel.setText(TaskPriority.NORMAL.name()); + } + + return area; + } + + void createTagText(final Composite parent) { + final var label = new Label(parent, SWT.NONE); + label.setText(TMUIMessages.TaskTagsPreferencePage_column_tag); + txtTag = new Text(parent, SWT.BORDER); + final var layoutData = new GridData(); + layoutData.grabExcessHorizontalSpace = true; + layoutData.horizontalAlignment = GridData.FILL; + txtTag.setLayoutData(layoutData); + txtTag.addListener(SWT.Modify, this::validateInput); + } + + void createTypeCombo(final Composite parent) { + final var label = new Label(parent, SWT.NONE); + label.setText(TMUIMessages.TaskTagsPreferencePage_column_type); + cmbType = new Combo(parent, SWT.READ_ONLY); + cmbType.setItems(Stream.of(MarkerConfig.Type.values()).map(Enum::name).toArray(String[]::new)); + cmbType.addListener(SWT.Modify, (final @Nullable Event e) -> { + if (!cmbType.getText().isBlank()) + switch (MarkerConfig.Type.valueOf(cmbType.getText())) { + case PROBLEM: + lblLevel.setText("Severity"); + cmbLevel.setItems(Stream.of(ProblemSeverity.values()).map(Enum::name).toArray(String[]::new)); + break; + case TASK: + lblLevel.setText("Priority"); + cmbLevel.setItems(Stream.of(TaskPriority.values()).map(Enum::name).toArray(String[]::new)); + break; + } + validateInput(null); + }); + } + + void createLevel(final Composite parent) { + lblLevel = new Label(parent, SWT.NONE); + final var layoutData = new GridData(); + layoutData.widthHint = computeMinimumColumnWidth("1234567890"); + layoutData.horizontalAlignment = GridData.FILL; + lblLevel.setLayoutData(layoutData); + cmbLevel = new Combo(parent, SWT.READ_ONLY); + cmbLevel.addListener(SWT.Modify, this::validateInput); + } + + @Override + public boolean isHelpAvailable() { + return false; + } + } + + private final Set markerConfigs = PreferenceHelper.loadMarkerConfigs(); + private TableViewer markerConfigsTable = lazyNonNull(); + + public TaskTagsPreferencePage() { + setDescription(TMUIMessages.TaskTagsPreferencePage_description); + } + + @Override + protected Control createContents(final @Nullable Composite ancestor) { + final var parent = new Composite(ancestor, SWT.NONE); + final var layout = new GridLayout(); + layout.numColumns = 2; + layout.marginHeight = 0; + layout.marginWidth = 0; + parent.setLayout(layout); + + final var table = new Table(parent, SWT.BORDER | SWT.FULL_SELECTION | SWT.H_SCROLL | SWT.V_SCROLL | SWT.SINGLE); + table.setLayoutData(new GridData(GridData.FILL_BOTH)); + table.setHeaderVisible(true); + table.setLinesVisible(true); + + markerConfigsTable = new TableViewer(table); + markerConfigsTable.setLabelProvider(new TableLabelProvider()); + markerConfigsTable.setContentProvider(new IStructuredContentProvider() { + @SuppressWarnings("unchecked") + @Override + public Object[] getElements(final @Nullable Object inputElement) { + return inputElement == null ? new MarkerConfig[0] : ((Collection) inputElement).toArray(); + } + }); + + final var tableColumnSorter = new ColumnViewerComparator(); + markerConfigsTable.setComparator(tableColumnSorter); + + final var column1 = new TableColumn(table, SWT.NONE); + column1.setText(TMUIMessages.TaskTagsPreferencePage_column_tag); + column1.setWidth(computeMinimumColumnWidth("1234567890")); + column1.addSelectionListener(new ColumnSelectionAdapter(column1, markerConfigsTable, 0, tableColumnSorter)); + + final var column2 = new TableColumn(table, SWT.NONE); + column2.setText(TMUIMessages.TaskTagsPreferencePage_column_type); + column2.setWidth(column1.getWidth()); + column2.addSelectionListener(new ColumnSelectionAdapter(column2, markerConfigsTable, 1, tableColumnSorter)); + + final var column3 = new TableColumn(table, SWT.NONE); + column3.setText(TMUIMessages.TaskTagsPreferencePage_column_level); + column3.setWidth(column1.getWidth()); + column3.addSelectionListener(new ColumnSelectionAdapter(column3, markerConfigsTable, 2, tableColumnSorter)); + + // Specify default sorting + table.setSortColumn(column1); + table.setSortDirection(tableColumnSorter.getDirection()); + + final var buttons = new Composite(parent, SWT.NONE); + buttons.setLayoutData(new GridData(GridData.VERTICAL_ALIGN_BEGINNING)); + buttons.setLayout(new GridLayout()); + final var newTagButton = new Button(buttons, SWT.PUSH); + newTagButton.setText(TMUIMessages.Button_new); + newTagButton.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); + newTagButton.addListener(SWT.Selection, (final @Nullable Event e) -> { + final var dlg = new MarkerConfigEditDialog(getShell(), null); + if (dlg.open() == Window.OK) { + markerConfigs.add(castNonNull(dlg.markerConfig)); + markerConfigsTable.refresh(); + } + }); + final var editTagButton = new Button(buttons, SWT.PUSH); + editTagButton.setText(TMUIMessages.Button_edit); + editTagButton.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); + editTagButton.addListener(SWT.Selection, (final @Nullable Event e) -> { + final var selection = (MarkerConfig) ((IStructuredSelection) markerConfigsTable.getSelection()).getFirstElement(); + if (selection != null) { + final var dlg = new MarkerConfigEditDialog(getShell(), selection); + if (dlg.open() == Window.OK) { + if (!selection.equals(dlg.markerConfig)) { + markerConfigs.remove(selection); + markerConfigs.add(castNonNull(dlg.markerConfig)); + markerConfigsTable.refresh(); + } + } + } + }); + final var removeTagButton = new Button(buttons, SWT.PUSH); + removeTagButton.setText(TMUIMessages.Button_remove); + removeTagButton.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); + removeTagButton.addListener(SWT.Selection, (final @Nullable Event e) -> { + final var selection = (MarkerConfig) ((IStructuredSelection) markerConfigsTable.getSelection()).getFirstElement(); + if (selection != null) { + markerConfigs.remove(selection); + markerConfigsTable.refresh(); + } + }); + applyDialogFont(parent); + + markerConfigsTable.setInput(markerConfigs); + + return parent; + } + + private int computeMinimumColumnWidth(final String string) { + final GC gc = new GC(getShell()); + try { + gc.setFont(JFaceResources.getDialogFont()); + return gc.stringExtent(string).x + 20; // pad 20 to accommodate table header trimmings + } finally { + gc.dispose(); + } + } + + @Override + public void init(final @Nullable IWorkbench workbench) { + } + + @Override + protected void performDefaults() { + markerConfigs.clear(); + markerConfigs.addAll(MarkerConfig.getDefaults()); + markerConfigsTable.refresh(); + } + + @Override + public boolean performOk() { + PreferenceHelper.saveMarkerConfigs(markerConfigs); + MarkerUtils.reloadMarkerConfigs(); + return true; + } +} diff --git a/org.eclipse.tm4e.ui/src/main/java/org/eclipse/tm4e/ui/internal/preferences/TextMatePreferencePage.java b/org.eclipse.tm4e.ui/src/main/java/org/eclipse/tm4e/ui/internal/preferences/TextMatePreferencePage.java index a4657584e..cfe6e894c 100644 --- a/org.eclipse.tm4e.ui/src/main/java/org/eclipse/tm4e/ui/internal/preferences/TextMatePreferencePage.java +++ b/org.eclipse.tm4e.ui/src/main/java/org/eclipse/tm4e/ui/internal/preferences/TextMatePreferencePage.java @@ -38,14 +38,16 @@ protected Control createContents(@Nullable final Composite parent) { composite.setLayout(layout); // Add link to grammar preference page - addRelatedLink(composite, GrammarPreferencePage.PAGE_ID, - TMUIMessages.TextMatePreferencePage_GrammarRelatedLink); + addRelatedLink(composite, GrammarPreferencePage.PAGE_ID, TMUIMessages.TextMatePreferencePage_GrammarRelatedLink); // Add link to language configuration preference page addRelatedLink(composite, "org.eclipse.tm4e.languageconfiguration.preferences.LanguageConfigurationPreferencePage", //$NON-NLS-1$ TMUIMessages.TextMatePreferencePage_LanguageConfigurationRelatedLink); + // Add link to task tags preference page + addRelatedLink(composite, TaskTagsPreferencePage.PAGE_ID, TMUIMessages.TextMatePreferencePage_TaskTagsRelatedLink); + // Add link to theme preference page addRelatedLink(composite, ThemePreferencePage.PAGE_ID, TMUIMessages.TextMatePreferencePage_ThemeRelatedLink); @@ -64,6 +66,5 @@ private void addRelatedLink(final Composite parent, final String pageId, final S @Override public void init(@Nullable final IWorkbench workbench) { - } } diff --git a/org.eclipse.tm4e.ui/src/main/java/org/eclipse/tm4e/ui/internal/utils/MarkerConfig.java b/org.eclipse.tm4e.ui/src/main/java/org/eclipse/tm4e/ui/internal/utils/MarkerConfig.java new file mode 100644 index 000000000..0105fe0f3 --- /dev/null +++ b/org.eclipse.tm4e.ui/src/main/java/org/eclipse/tm4e/ui/internal/utils/MarkerConfig.java @@ -0,0 +1,153 @@ +/******************************************************************************* + * Copyright (c) 2023 Vegard IT GmbH and others. + * This program and the Faccompanying 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: + * Sebastian Thomschke (Vegard IT GmbH) - initial implementation + *******************************************************************************/ +package org.eclipse.tm4e.ui.internal.utils; + +import java.util.HashSet; +import java.util.Objects; +import java.util.Set; + +import org.eclipse.core.resources.IMarker; +import org.eclipse.jdt.annotation.Nullable; + +public abstract class MarkerConfig { + + public static Set getDefaults() { + final var defaults = new HashSet(); + // problem markers: + defaults.add(new ProblemMarkerConfig("ATTN", ProblemSeverity.INFO)); + defaults.add(new ProblemMarkerConfig("NOTE", ProblemSeverity.INFO)); + // task markers: + defaults.add(new TaskMarkerConfig("BUG", TaskPriority.HIGH)); + defaults.add(new TaskMarkerConfig("FIXME", TaskPriority.HIGH)); + defaults.add(new TaskMarkerConfig("HACK", TaskPriority.NORMAL)); + defaults.add(new TaskMarkerConfig("OPTIMIZE", TaskPriority.NORMAL)); + defaults.add(new TaskMarkerConfig("TODO", TaskPriority.NORMAL)); + defaults.add(new TaskMarkerConfig("XXX", TaskPriority.NORMAL)); + return defaults; + } + + public static final class ProblemMarkerConfig extends MarkerConfig { + public ProblemMarkerConfig(final String tag, final ProblemSeverity severity) { + super(tag, Type.PROBLEM); + this.severity = severity; + } + + public final ProblemSeverity severity; + + @Override + public boolean equals(final @Nullable Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + ProblemMarkerConfig other = (ProblemMarkerConfig) obj; + return Objects.equals(tag, other.tag) && type == other.type && severity == other.severity; + } + + @Override + public int hashCode() { + return Objects.hash(tag, type, severity); + } + + @Override + public String toString() { + return "ProblemMarkerConfig [tag=" + tag + ", severity=" + severity + "]"; + } + } + + public enum ProblemSeverity { + ERROR(IMarker.SEVERITY_ERROR), + WARNING(IMarker.SEVERITY_WARNING), + INFO(IMarker.SEVERITY_INFO); + + ProblemSeverity(final int value) { + this.value = value; + } + + public final int value; + } + + public static final class TaskMarkerConfig extends MarkerConfig { + + public TaskMarkerConfig(final String tag, final TaskPriority priority) { + super(tag, Type.TASK); + this.priority = priority; + } + + public final TaskPriority priority; + + @Override + public boolean equals(final @Nullable Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + TaskMarkerConfig other = (TaskMarkerConfig) obj; + return Objects.equals(tag, other.tag) && type == other.type && priority == other.priority; + } + + @Override + public int hashCode() { + return Objects.hash(tag, type, priority); + } + + @Override + public String toString() { + return "TaskMarkerConfig [tag=" + tag + ", priority=" + priority + "]"; + } + } + + public enum TaskPriority { + HIGH(IMarker.PRIORITY_HIGH), + NORMAL(IMarker.PRIORITY_NORMAL), + LOW(IMarker.PRIORITY_LOW); + + TaskPriority(final int value) { + this.value = value; + } + + public final int value; + } + + public enum Type { + PROBLEM, + TASK; + } + + private MarkerConfig(final String tag, final Type type) { + this.tag = tag; + this.type = type; + } + + public final String tag; + public final Type type; + + public boolean isProblem() { + return type == Type.PROBLEM; + } + + public boolean isTask() { + return type == Type.TASK; + } + + public ProblemMarkerConfig asProblemMarkerConfig() { + return (ProblemMarkerConfig) this; + } + + public TaskMarkerConfig asTaskMarkerConfig() { + return (TaskMarkerConfig) this; + } +} diff --git a/org.eclipse.tm4e.ui/src/main/java/org/eclipse/tm4e/ui/internal/utils/MarkerUtils.java b/org.eclipse.tm4e.ui/src/main/java/org/eclipse/tm4e/ui/internal/utils/MarkerUtils.java index 9c0f825ae..1e1ae5143 100644 --- a/org.eclipse.tm4e.ui/src/main/java/org/eclipse/tm4e/ui/internal/utils/MarkerUtils.java +++ b/org.eclipse.tm4e.ui/src/main/java/org/eclipse/tm4e/ui/internal/utils/MarkerUtils.java @@ -9,7 +9,7 @@ */ package org.eclipse.tm4e.ui.internal.utils; -import static org.eclipse.tm4e.core.internal.utils.NullSafetyHelper.*; +import static org.eclipse.tm4e.core.internal.utils.NullSafetyHelper.castNonNull; import java.util.ArrayList; import java.util.Collections; @@ -22,14 +22,13 @@ import org.eclipse.core.resources.IMarker; import org.eclipse.core.runtime.CoreException; -import org.eclipse.jdt.annotation.NonNull; -import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; import org.eclipse.tm4e.core.model.ITMModel; import org.eclipse.tm4e.core.model.ModelTokensChangedEvent; import org.eclipse.tm4e.core.model.TMToken; import org.eclipse.tm4e.ui.TMUIPlugin; import org.eclipse.tm4e.ui.internal.model.TMDocumentModel; +import org.eclipse.tm4e.ui.internal.preferences.PreferenceHelper; public final class MarkerUtils { @@ -37,30 +36,22 @@ public final class MarkerUtils { private static final String PROBLEMMARKER_TYPE = "org.eclipse.tm4e.ui.problemmarker"; private static final String TASKMARKER_TYPE = "org.eclipse.tm4e.ui.taskmarker"; - @NonNullByDefault({}) - private record MarkerConfig(@NonNull String type, int priority, int severity) { - static MarkerConfig forProblem(final int severity) { - return new MarkerConfig(PROBLEMMARKER_TYPE, IMarker.PRIORITY_NORMAL, severity); - } + private static final Map MARKERCONFIG_BY_TAG = new HashMap<>(); + static { + reloadMarkerConfigs(); + } + private static final Pattern TAG_SELECTOR_PATTERN = Pattern.compile( + "^\\s(" + MARKERCONFIG_BY_TAG.keySet().stream().collect(Collectors.joining("|")) + ")\\b"); - static MarkerConfig forTask(final int priority) { - return new MarkerConfig(TASKMARKER_TYPE, priority, IMarker.SEVERITY_INFO); + public static void reloadMarkerConfigs() { + synchronized (MARKERCONFIG_BY_TAG) { + MARKERCONFIG_BY_TAG.clear(); + for (final var markerConfig : PreferenceHelper.loadMarkerConfigs()) { + MARKERCONFIG_BY_TAG.put(markerConfig.tag, markerConfig); + } } } - private static final Map MARKERCONFIG_BY_TAG = Map.of( - // problem markers: - "BUG", MarkerConfig.forProblem(IMarker.SEVERITY_ERROR), - "NOTE", MarkerConfig.forProblem(IMarker.SEVERITY_INFO), - // task markers: - "FIXME", MarkerConfig.forTask(IMarker.PRIORITY_HIGH), - "HACK", MarkerConfig.forTask(IMarker.PRIORITY_NORMAL), - "TODO", MarkerConfig.forTask(IMarker.PRIORITY_NORMAL), - "XXX", MarkerConfig.forTask(IMarker.PRIORITY_NORMAL)); - - private static final Pattern TAG_SELECTOR_PATTERN = Pattern.compile( - "\\b(" + MARKERCONFIG_BY_TAG.keySet().stream().collect(Collectors.joining("|")) + ")\\b"); - /** * Updates all TM4E text markers of the corresponding document starting from * event.ranges.get(0).fromLineNumber until the end of the document. @@ -82,7 +73,7 @@ public static void updateTextMarkers(final ModelTokensChangedEvent event) { * * @param startLineNumber 1-based */ - public static void updateTextMarkers(final TMDocumentModel docModel, final int startLineNumber) + private static void updateTextMarkers(final TMDocumentModel docModel, final int startLineNumber) throws CoreException { final var doc = docModel.getDocument(); @@ -148,17 +139,23 @@ public static void updateTextMarkers(final TMDocumentModel docModel, final int s final var attrs = new HashMap(); attrs.put(IMarker.LINE_NUMBER, lineNumberObj); attrs.put(IMarker.MESSAGE, markerText); - attrs.put(IMarker.PRIORITY, markerConfig.priority); - attrs.put(IMarker.SEVERITY, markerConfig.severity); + switch (markerConfig.type) { + case PROBLEM -> attrs.put(IMarker.SEVERITY, markerConfig.asProblemMarkerConfig().severity); + case TASK -> attrs.put(IMarker.PRIORITY, markerConfig.asTaskMarkerConfig().priority); + } attrs.put(IMarker.USER_EDITABLE, Boolean.FALSE); attrs.put(IMarker.SOURCE_ID, "TM4E"); // only create a new marker if no matching marker already exists - if (!removeMatchingMarker(outdatedMarkers, markerConfig.type, attrs)) { + String markerTypeId = switch (markerConfig.type) { + case PROBLEM -> PROBLEMMARKER_TYPE; + case TASK -> TASKMARKER_TYPE; + }; + if (!removeMatchingMarker(outdatedMarkers, markerTypeId, attrs)) { final var markerTextStartOffset = lineOffset + token.startIndex + matcher.start(); attrs.put(IMarker.CHAR_START, markerTextStartOffset); attrs.put(IMarker.CHAR_END, markerTextStartOffset + markerText.length()); - res.createMarker(markerConfig.type, attrs); + res.createMarker(markerTypeId, attrs); } } catch (final Exception ex) { TMUIPlugin.logError(ex); diff --git a/org.eclipse.tm4e.ui/src/main/java/org/eclipse/tm4e/ui/internal/widgets/ColumnViewerComparator.java b/org.eclipse.tm4e.ui/src/main/java/org/eclipse/tm4e/ui/internal/widgets/ColumnViewerComparator.java index 20415ea35..bd867da7e 100644 --- a/org.eclipse.tm4e.ui/src/main/java/org/eclipse/tm4e/ui/internal/widgets/ColumnViewerComparator.java +++ b/org.eclipse.tm4e.ui/src/main/java/org/eclipse/tm4e/ui/internal/widgets/ColumnViewerComparator.java @@ -20,7 +20,7 @@ import org.eclipse.swt.SWT; /** - * Viewer compoarator which sort a given column. + * Viewer comparator which sort a given column. * */ public final class ColumnViewerComparator extends ViewerComparator { diff --git a/org.eclipse.tm4e.ui/src/test/java/org/eclipse/tm4e/ui/internal/preferences/PreferenceHelperTest.java b/org.eclipse.tm4e.ui/src/test/java/org/eclipse/tm4e/ui/internal/preferences/PreferenceHelperTest.java new file mode 100644 index 000000000..d4aae1a9f --- /dev/null +++ b/org.eclipse.tm4e.ui/src/test/java/org/eclipse/tm4e/ui/internal/preferences/PreferenceHelperTest.java @@ -0,0 +1,27 @@ +/******************************************************************************* + * Copyright (c) 2023 Vegard IT GmbH and others. + * This program and the Faccompanying 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: + * Sebastian Thomschke (Vegard IT GmbH) - initial implementation + *******************************************************************************/ +package org.eclipse.tm4e.ui.internal.preferences; + +import static org.junit.jupiter.api.Assertions.*; + +import org.eclipse.tm4e.ui.internal.utils.MarkerConfig; +import org.junit.jupiter.api.Test; + +class PreferenceHelperTest { + + @Test + void testMarkerConfigsSerialization() { + final var defaults = MarkerConfig.getDefaults(); + final var defaultsAsJson = PreferenceHelper.toJsonMarkerConfigs(defaults); + assertEquals(defaults, PreferenceHelper.loadMarkerConfigs(defaultsAsJson)); + } +}