From 7b8d50f5faedee83a47e505e297a0a93232a1f72 Mon Sep 17 00:00:00 2001 From: sunarya-thito Date: Tue, 14 Sep 2021 11:38:29 +0700 Subject: [PATCH 1/6] Added YAML support and prefix support for completions --- .../easyi18n/dialog/SettingsDialog.java | 12 +- .../editor/KeyCompletionProvider.java | 43 +++- .../io/implementation/YamlTranslatorIO.java | 117 +++++++++ .../marhali/easyi18n/model/SettingsState.java | 9 + .../java/de/marhali/easyi18n/util/IOUtil.java | 7 +- .../thito/nodeflow/config/ListSection.java | 227 ++++++++++++++++++ .../thito/nodeflow/config/MapSection.java | 73 ++++++ .../java/thito/nodeflow/config/Section.java | 215 +++++++++++++++++ src/main/resources/messages.properties | 3 +- 9 files changed, 688 insertions(+), 18 deletions(-) create mode 100644 src/main/java/de/marhali/easyi18n/io/implementation/YamlTranslatorIO.java create mode 100644 src/main/java/thito/nodeflow/config/ListSection.java create mode 100644 src/main/java/thito/nodeflow/config/MapSection.java create mode 100644 src/main/java/thito/nodeflow/config/Section.java diff --git a/src/main/java/de/marhali/easyi18n/dialog/SettingsDialog.java b/src/main/java/de/marhali/easyi18n/dialog/SettingsDialog.java index fc1eb92..75b3306 100644 --- a/src/main/java/de/marhali/easyi18n/dialog/SettingsDialog.java +++ b/src/main/java/de/marhali/easyi18n/dialog/SettingsDialog.java @@ -27,6 +27,7 @@ public class SettingsDialog { private TextFieldWithBrowseButton pathText; private JBTextField filePatternText; private JBTextField previewText; + private JBTextField prefixText; private JBCheckBox codeAssistanceCheckbox; public SettingsDialog(Project project) { @@ -37,20 +38,22 @@ public class SettingsDialog { String localesPath = SettingsService.getInstance(project).getState().getLocalesPath(); String filePattern = SettingsService.getInstance(project).getState().getFilePattern(); String previewLocale = SettingsService.getInstance(project).getState().getPreviewLocale(); + String prefixLocale = SettingsService.getInstance(project).getState().getPrefix(); boolean codeAssistance = SettingsService.getInstance(project).getState().isCodeAssistance(); - if(prepare(localesPath, filePattern, previewLocale, codeAssistance).show() == DialogWrapper.OK_EXIT_CODE) { // Save changes + if(prepare(localesPath, filePattern, previewLocale, prefixLocale, codeAssistance).show() == DialogWrapper.OK_EXIT_CODE) { // Save changes SettingsService.getInstance(project).getState().setLocalesPath(pathText.getText()); SettingsService.getInstance(project).getState().setFilePattern(filePatternText.getText()); SettingsService.getInstance(project).getState().setPreviewLocale(previewText.getText()); SettingsService.getInstance(project).getState().setCodeAssistance(codeAssistanceCheckbox.isSelected()); + SettingsService.getInstance(project).getState().setPrefix(prefixText.getText()); // Reload instance DataStore.getInstance(project).reloadFromDisk(); } } - private DialogBuilder prepare(String localesPath, String filePattern, String previewLocale, boolean codeAssistance) { + private DialogBuilder prepare(String localesPath, String filePattern, String previewLocale, String prefixLocale, boolean codeAssistance) { JPanel rootPanel = new JPanel(new GridLayout(0, 1, 2, 2)); JBLabel pathLabel = new JBLabel(ResourceBundle.getBundle("messages").getString("settings.path.text")); @@ -80,6 +83,11 @@ public class SettingsDialog { codeAssistanceCheckbox = new JBCheckBox(ResourceBundle.getBundle("messages").getString("settings.editor.assistance")); codeAssistanceCheckbox.setSelected(codeAssistance); + JBLabel prefixLabel = new JBLabel(ResourceBundle.getBundle("messages").getString("settings.path.prefix")); + prefixText = new JBTextField(prefixLocale); + rootPanel.add(prefixLabel); + rootPanel.add(prefixText); + rootPanel.add(codeAssistanceCheckbox); DialogBuilder builder = new DialogBuilder(); diff --git a/src/main/java/de/marhali/easyi18n/editor/KeyCompletionProvider.java b/src/main/java/de/marhali/easyi18n/editor/KeyCompletionProvider.java index 27633aa..e0bae3b 100644 --- a/src/main/java/de/marhali/easyi18n/editor/KeyCompletionProvider.java +++ b/src/main/java/de/marhali/easyi18n/editor/KeyCompletionProvider.java @@ -34,16 +34,18 @@ public class KeyCompletionProvider extends CompletionProvider sections = TranslationsUtil.getSections(query); - String lastSection = null; - - if(!sections.isEmpty() && !query.endsWith(".")) { - lastSection = sections.remove(sections.size() - 1); - } String path = TranslationsUtil.sectionsToFullPath(sections); + if (prefix != null && path.startsWith(prefix)) { + path = path.substring(prefix.length()); + if (path.startsWith(".")) { + path = path.substring(1); + } + } LocalizedNode node = sections.isEmpty() ? DataStore.getInstance(project).getTranslations().getNodes() : DataStore.getInstance(project).getTranslations().getNode(path); @@ -52,13 +54,32 @@ public class KeyCompletionProvider extends CompletionProvider callback) { + ApplicationManager.getApplication().saveAll(); // Save opened files (required if new locales were added) + + ApplicationManager.getApplication().runReadAction(() -> { + VirtualFile directory = LocalFileSystem.getInstance().findFileByIoFile(new File(directoryPath)); + + if(directory == null || directory.getChildren() == null) { + throw new IllegalArgumentException("Specified folder is invalid (" + directoryPath + ")"); + } + + VirtualFile[] files = directory.getChildren(); + + List locales = new ArrayList<>(); + LocalizedNode nodes = new LocalizedNode(LocalizedNode.ROOT_KEY, new ArrayList<>()); + + try { + for(VirtualFile file : files) { + + if(!IOUtil.isFileRelevant(project, file)) { // File does not matches pattern + continue; + } + + locales.add(file.getNameWithoutExtension()); + + try (Reader reader = new InputStreamReader(file.getInputStream(), StandardCharsets.UTF_8)) { + Section section = Section.parseToMap(reader); + load(file.getNameWithoutExtension(), nodes, section); + } + } + + callback.accept(new Translations(locales, nodes)); + + } catch(IOException e) { + e.printStackTrace(); + callback.accept(null); + } + }); + } + + private void load(String locale, LocalizedNode node, Section section) { + if (section instanceof MapSection) { + for (String key : section.getKeys()) { + LocalizedNode child = node.getChildren(key); + if (child == null) { + node.addChildren(child = new LocalizedNode(key, new ArrayList<>())); + } + LocalizedNode finalChild = child; + MapSection map = section.getMap(key).orElse(null); + if (map != null) { + load(locale, finalChild, map); + } else { + String value = section.getString(key).orElse(null); + if (value != null) { + child.getValue().put(locale, value); + } + } + } + } + } + + private void save(LocalizedNode node, String locale, Section section, String path) { + if (node.isLeaf() && !node.getKey().equals(LocalizedNode.ROOT_KEY)) { + String value = node.getValue().get(locale); + if (value != null) { + section.set(path, value); + } + } else { + for (LocalizedNode child : node.getChildren()) { + save(child, locale, section, path == null ? child.getKey() : path + "." + child.getKey()); + } + } + } + + @Override + public void save(@NotNull Project project, @NotNull Translations translations, @NotNull String directoryPath, @NotNull Consumer callback) { + ApplicationManager.getApplication().runWriteAction(() -> { + try { + for(String locale : translations.getLocales()) { + Section section = new MapSection(); + + save(translations.getNodes(), locale, section, null); + + String fullPath = directoryPath + "/" + locale + ".yml"; + VirtualFile file = LocalFileSystem.getInstance().findFileByIoFile(new File(fullPath)); + + file.setBinaryContent(Section.toString(section).getBytes(file.getCharset())); + } + + // Successfully saved + callback.accept(true); + + } catch(IOException e) { + e.printStackTrace(); + callback.accept(false); + } + }); + } +} diff --git a/src/main/java/de/marhali/easyi18n/model/SettingsState.java b/src/main/java/de/marhali/easyi18n/model/SettingsState.java index ba2dfb8..309562b 100644 --- a/src/main/java/de/marhali/easyi18n/model/SettingsState.java +++ b/src/main/java/de/marhali/easyi18n/model/SettingsState.java @@ -16,6 +16,7 @@ public class SettingsState { private String localesPath; private String filePattern; private String previewLocale; + private String prefix; private Boolean codeAssistance; public SettingsState() {} @@ -51,4 +52,12 @@ public class SettingsState { public void setCodeAssistance(boolean codeAssistance) { this.codeAssistance = codeAssistance; } + + public void setPrefix(String prefix) { + this.prefix = prefix; + } + + public String getPrefix() { + return prefix; + } } \ No newline at end of file diff --git a/src/main/java/de/marhali/easyi18n/util/IOUtil.java b/src/main/java/de/marhali/easyi18n/util/IOUtil.java index 76bfa00..b5e81f1 100644 --- a/src/main/java/de/marhali/easyi18n/util/IOUtil.java +++ b/src/main/java/de/marhali/easyi18n/util/IOUtil.java @@ -3,9 +3,7 @@ package de.marhali.easyi18n.util; import com.intellij.openapi.project.Project; import com.intellij.openapi.vfs.LocalFileSystem; import com.intellij.openapi.vfs.VirtualFile; -import de.marhali.easyi18n.io.implementation.JsonTranslatorIO; -import de.marhali.easyi18n.io.implementation.ModularizedJsonTranslatorIO; -import de.marhali.easyi18n.io.implementation.PropertiesTranslatorIO; +import de.marhali.easyi18n.io.implementation.*; import de.marhali.easyi18n.io.TranslatorIO; import de.marhali.easyi18n.service.SettingsService; @@ -50,7 +48,8 @@ public class IOUtil { case "properties": return new PropertiesTranslatorIO(); - + case "yml": + return new YamlTranslatorIO(); default: throw new UnsupportedOperationException("Unsupported i18n locale file format: " + any.get().getFileType().getDefaultExtension()); diff --git a/src/main/java/thito/nodeflow/config/ListSection.java b/src/main/java/thito/nodeflow/config/ListSection.java new file mode 100644 index 0000000..c5ae57e --- /dev/null +++ b/src/main/java/thito/nodeflow/config/ListSection.java @@ -0,0 +1,227 @@ +package thito.nodeflow.config; + +import java.util.*; +import java.util.stream.*; + +public class ListSection extends ArrayList implements Section { + private Section parent; + private String name; + + public ListSection() { + super(); + } + + public ListSection(Collection c) { + super(); + addAll(c); + } + + protected void setParent(Section parent, String name) { + this.parent = parent; + this.name = name; + } + + @Override + public String getName() { + if (name == null && parent != null) { + if (parent instanceof ListSection) { + return String.valueOf(((ListSection) parent).indexOf(this)); + } + if (parent instanceof MapSection) { + return ((MapSection) parent).entrySet().stream().filter(e -> Objects.equals(e.getValue(), this)) + .findAny().map(Map.Entry::getKey).orElse(null); + } + } + return name; + } + + public Optional getObject(int index) { + return index >= 0 && index < size() ? Optional.ofNullable(get(index)) : Optional.empty(); + } + + @Override + public Section getParent() { + return parent; + } + + @Override + public Set getKeys() { + return IntStream.range(0, size()).mapToObj(String::valueOf).collect(Collectors.toSet()); + } + + @Override + public Optional getInScope(String key) { + try { + return Optional.ofNullable(get(Integer.parseInt(key))); + } catch (Throwable t) { + } + return Optional.empty(); + } + + @Override + public void setInScope(String key, Object value) { + try { + set(Integer.parseInt(key), value); + } catch (Throwable t) { + } + } + + @Override + public Object set(int index, Object element) { + element = Section.wrap(element); + if (element instanceof Section) element = Section.wrapParent(this, null, (Section) element); + return super.set(index, element); + } + + @Override + public boolean add(Object o) { + o = Section.wrap(o); + if (o instanceof Section) o = Section.wrapParent(this, null, (Section) o); + return super.add(o); + } + + @Override + public void add(int index, Object element) { + element = Section.wrap(element); + if (element instanceof Section) element = Section.wrapParent(this, null, (Section) element); + super.add(index, element); + } + + @Override + public boolean addAll(Collection c) { + c.forEach(o -> add(o)); + return !c.isEmpty(); + } + + @Override + public boolean addAll(int index, Collection c) { + List wrapped = new ArrayList<>(); + c.forEach(obj -> { + Object o = Section.wrap(obj); + if (o instanceof Section) o = Section.wrapParent(this, null, (Section) o); + wrapped.add(o); + }); + return super.addAll(index, wrapped); + } + + @Override + public String toString() { + return Section.toString(this); + } + + public > Optional getEnum(int index, Class clz) { + return getObject(index).map(o -> { + try { + return Enum.valueOf(clz, String.valueOf(o)); + } catch (Throwable t) { + return null; + } + }); + } + + public Optional getString(int index) { + return getObject(index).map(String::valueOf); + } + + public Optional getInteger(int index) { + return getObject(index).map(o -> { + try { + return Integer.valueOf(String.valueOf(o)); + } catch (Throwable t) { + return null; + } + }); + } + + public Optional getDouble(int index) { + return getObject(index).map(o -> { + try { + return Double.valueOf(String.valueOf(o)); + } catch (Throwable t) { + return null; + } + }); + } + + public Optional getLong(int index) { + return getObject(index).map(o -> { + try { + return Long.valueOf(String.valueOf(o)); + } catch (Throwable t) { + return null; + } + }); + } + + public Optional getFloat(int index) { + return getObject(index).map(o -> { + try { + return Float.valueOf(String.valueOf(o)); + } catch (Throwable t) { + return null; + } + }); + } + + public Optional getShort(int index) { + return getObject(index).map(o -> { + try { + return Short.valueOf(String.valueOf(o)); + } catch (Throwable t) { + return null; + } + }); + } + + public Optional getByte(int index) { + return getObject(index).map(o -> { + try { + return Byte.valueOf(String.valueOf(o)); + } catch (Throwable t) { + return null; + } + }); + } + + public Optional getCharacter(int index) { + return getObject(index).map(o -> { + String text = String.valueOf(o); + return text.isEmpty() ? null : text.charAt(0); + }); + } + + public Optional getBoolean(int index) { + return getObject(index).map(o -> { + String text = String.valueOf(o); + return text.equals("true") ? Boolean.TRUE : text.equals("false") ? Boolean.FALSE : null; + }); + } + + public Optional getMap(int index) { + return getObject(index).map(o -> { + if (o instanceof Map) { + if (o instanceof MapSection) return (MapSection) o; + MapSection mapSection = new MapSection((Map) o); + mapSection.setParent(this, null); + return mapSection; + } + return null; + }); + } + + public Optional getList(int index) { + return getObject(index).map(o -> { + if (o instanceof List) { + if (o instanceof ListSection) { + return (ListSection) o; + } + ListSection list = new ListSection((List) o); + list.setParent(this, null); + return list; + } + ListSection list = new ListSection(Collections.singleton(o)); + list.setParent(this, null); + return list; + }); + } +} diff --git a/src/main/java/thito/nodeflow/config/MapSection.java b/src/main/java/thito/nodeflow/config/MapSection.java new file mode 100644 index 0000000..4581c3f --- /dev/null +++ b/src/main/java/thito/nodeflow/config/MapSection.java @@ -0,0 +1,73 @@ +package thito.nodeflow.config; + +import java.util.*; + +public class MapSection extends HashMap implements Section { + private Section parent; + private String name; + + public MapSection() { + super(); + } + + public MapSection(Map m) { + super(); + m.forEach((key, value) -> put(String.valueOf(key), value)); + } + + protected void setParent(Section parent, String name) { + this.parent = parent; + this.name = name; + } + + @Override + public String getName() { + if (name == null && parent != null) { + if (parent instanceof ListSection) { + return String.valueOf(((ListSection) parent).indexOf(this)); + } + if (parent instanceof MapSection) { + return ((MapSection) parent).entrySet().stream().filter(e -> Objects.equals(e.getValue(), this)) + .findAny().map(Entry::getKey).orElse(null); + } + } + return name; + } + + @Override + public Section getParent() { + return parent; + } + + @Override + public void setInScope(String key, Object value) { + put(key, value); + } + + @Override + public Object put(String key, Object value) { + value = Section.wrap(value); + if (value instanceof Section) value = Section.wrapParent(this, key, (Section) value); + return super.put(key, value); + } + + @Override + public void putAll(Map m) { + m.forEach(this::put); + } + + @Override + public Set getKeys() { + return keySet(); + } + + @Override + public Optional getInScope(String key) { + return Optional.ofNullable(get(key)); + } + + @Override + public String toString() { + return Section.toString(this); + } +} diff --git a/src/main/java/thito/nodeflow/config/Section.java b/src/main/java/thito/nodeflow/config/Section.java new file mode 100644 index 0000000..1a9a90f --- /dev/null +++ b/src/main/java/thito/nodeflow/config/Section.java @@ -0,0 +1,215 @@ +package thito.nodeflow.config; + +import org.yaml.snakeyaml.*; + +import java.io.*; +import java.util.*; +import java.util.regex.*; + +public interface Section { + String SEPARATOR = "."; + static Object wrap(Object o) { + if (o instanceof Section) return o; + if (o instanceof List) { + return new ListSection((List) o); + } + if (o instanceof Map) { + return new MapSection((Map) o); + } + return o; + } + static String getName(String path) { + String[] split = path.split(Pattern.quote(SEPARATOR)); + return split[split.length - 1]; + } + static Section wrapParent(Section parent, String name, Section current) { + if (current.getParent() != null && current.getParent() != parent) { + if (current instanceof MapSection) { + MapSection mapSection = new MapSection((MapSection) current); + mapSection.setParent(parent, name); + return mapSection; + } + if (current instanceof ListSection) { + ListSection objects = new ListSection((ListSection) current); + objects.setParent(parent, name); + return objects; + } + } else { + if (current instanceof MapSection) { + ((MapSection) current).setParent(parent, name); + } + if (current instanceof ListSection) { + ((ListSection) current).setParent(parent, name); + } + } + return current; + } + static String toString(Section section) { + DumperOptions options = new DumperOptions(); + options.setIndent(4); + options.setAllowUnicode(true); + options.setPrettyFlow(true); + options.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK); + Yaml yaml = new Yaml(options); + return yaml.dumpAsMap(section); + } + static MapSection parseToMap(Reader reader) { + Yaml yaml = new Yaml(); + return new MapSection(yaml.loadAs(reader, Map.class)); + } + Set getKeys(); + default Set getPaths() { + Set paths = new HashSet<>(); + for (String k : getKeys()) { + Object lookup = getInScope(k).orElse(null); + if (lookup instanceof Section) { + for (String p : ((Section) lookup).getPaths()) { + paths.add(k + "." + p); + } + } + } + return paths; + } + String getName(); + default String getPath() { + StringBuilder path = new StringBuilder(getName()); + Section parent; + while ((parent = getParent()) != null) { + path.insert(0, parent.getName() + SEPARATOR); + } + return path.toString(); + } + Section getParent(); + Optional getInScope(String key); + void setInScope(String key, Object value); + default void set(String path, Object value) { + String[] paths = path.split(Pattern.quote(SEPARATOR)); + Object lookup = this; + for (int i = 0; i < paths.length - 1; i++) { + Section oldLookup = (Section) lookup; + lookup = oldLookup.getInScope(paths[i]).orElse(null); + if (!(lookup instanceof Section)) { + oldLookup.setInScope(paths[i], lookup = new MapSection()); + } + } + if (paths.length > 0) { + ((Section) lookup).setInScope(paths[paths.length - 1], value); + } + } + default Optional getObject(String path) { + String[] paths = path.split(Pattern.quote(SEPARATOR)); + Object lookup = this; + for (String s : paths) { + if (lookup instanceof Section) { + lookup = ((Section) lookup).getInScope(s).orElse(null); + } else { + return Optional.empty(); + } + } + return Optional.ofNullable(lookup); + } + default > Optional getEnum(String path, Class clz) { + return getObject(path).map(o -> { + try { + return Enum.valueOf(clz, String.valueOf(o)); + } catch (Throwable t) { + return null; + } + }); + } + default Optional getString(String path) { + return getObject(path).map(String::valueOf); + } + default Optional getInteger(String path) { + return getObject(path).map(o -> { + try { + return Integer.valueOf(String.valueOf(o)); + } catch (Throwable t) { + return null; + } + }); + } + default Optional getDouble(String path) { + return getObject(path).map(o -> { + try { + return Double.valueOf(String.valueOf(o)); + } catch (Throwable t) { + return null; + } + }); + } + default Optional getLong(String path) { + return getObject(path).map(o -> { + try { + return Long.valueOf(String.valueOf(o)); + } catch (Throwable t) { + return null; + } + }); + } + default Optional getFloat(String path) { + return getObject(path).map(o -> { + try { + return Float.valueOf(String.valueOf(o)); + } catch (Throwable t) { + return null; + } + }); + } + default Optional getShort(String path) { + return getObject(path).map(o -> { + try { + return Short.valueOf(String.valueOf(o)); + } catch (Throwable t) { + return null; + } + }); + } + default Optional getByte(String path) { + return getObject(path).map(o -> { + try { + return Byte.valueOf(String.valueOf(o)); + } catch (Throwable t) { + return null; + } + }); + } + default Optional getCharacter(String path) { + return getObject(path).map(o -> { + String text = String.valueOf(o); + return text.isEmpty() ? null : text.charAt(0); + }); + } + default Optional getBoolean(String path) { + return getObject(path).map(o -> { + String text = String.valueOf(o); + return text.equals("true") ? Boolean.TRUE : text.equals("false") ? Boolean.FALSE : null; + }); + } + default Optional getMap(String path) { + return getObject(path).map(o -> { + if (o instanceof Map) { + if (o instanceof MapSection) return (MapSection) o; + MapSection mapSection = new MapSection((Map) o); + mapSection.setParent(this, Section.getName(path)); + return mapSection; + } + return null; + }); + } + default Optional getList(String path) { + return getObject(path).map(o -> { + if (o instanceof List) { + if (o instanceof ListSection) { + return (ListSection) o; + } + ListSection list = new ListSection((List) o); + list.setParent(this, Section.getName(path)); + return list; + } + ListSection list = new ListSection(Collections.singleton(o)); + list.setParent(this, Section.getName(path)); + return list; + }); + } +} diff --git a/src/main/resources/messages.properties b/src/main/resources/messages.properties index 727cd4f..1e21ba5 100644 --- a/src/main/resources/messages.properties +++ b/src/main/resources/messages.properties @@ -15,4 +15,5 @@ settings.path.title=Locales Directory settings.path.text=Locales directory settings.path.file-pattern=Translation file pattern settings.preview=Preview locale -settings.editor.assistance=I18n key completion and annotation inside editor \ No newline at end of file +settings.editor.assistance=I18n key completion and annotation inside editor +settings.path.prefix=Prefix \ No newline at end of file From d61cd57979bdda3175096d4a1a1eecaf4754018b Mon Sep 17 00:00:00 2001 From: sunarya-thito Date: Tue, 14 Sep 2021 18:53:50 +0700 Subject: [PATCH 2/6] Better prefix support --- .../editor/KeyCompletionProvider.java | 90 ++++++++----------- .../GenericKeyCompletionContributor.java | 5 +- 2 files changed, 40 insertions(+), 55 deletions(-) diff --git a/src/main/java/de/marhali/easyi18n/editor/KeyCompletionProvider.java b/src/main/java/de/marhali/easyi18n/editor/KeyCompletionProvider.java index e0bae3b..0092851 100644 --- a/src/main/java/de/marhali/easyi18n/editor/KeyCompletionProvider.java +++ b/src/main/java/de/marhali/easyi18n/editor/KeyCompletionProvider.java @@ -1,20 +1,14 @@ package de.marhali.easyi18n.editor; -import com.intellij.codeInsight.completion.CompletionParameters; -import com.intellij.codeInsight.completion.CompletionProvider; -import com.intellij.codeInsight.completion.CompletionResultSet; -import com.intellij.codeInsight.lookup.LookupElementBuilder; -import com.intellij.openapi.project.Project; -import com.intellij.util.ProcessingContext; +import com.intellij.codeInsight.completion.*; +import com.intellij.codeInsight.lookup.*; +import com.intellij.openapi.project.*; +import com.intellij.util.*; +import de.marhali.easyi18n.model.*; +import de.marhali.easyi18n.service.*; +import org.jetbrains.annotations.*; -import de.marhali.easyi18n.model.LocalizedNode; -import de.marhali.easyi18n.service.DataStore; -import de.marhali.easyi18n.service.SettingsService; -import de.marhali.easyi18n.util.TranslationsUtil; - -import org.jetbrains.annotations.NotNull; - -import java.util.List; +import java.util.*; /** * I18n translation key completion provider. @@ -36,56 +30,46 @@ public class KeyCompletionProvider extends CompletionProvider sections = TranslationsUtil.getSections(query); + String path = result.getPrefixMatcher().getPrefix(); - String path = TranslationsUtil.sectionsToFullPath(sections); - if (prefix != null && path.startsWith(prefix)) { - path = path.substring(prefix.length()); - if (path.startsWith(".")) { - path = path.substring(1); + if (path.endsWith(".")) { + path = path.substring(0, path.length() - 1); + } + + DataStore instance = DataStore.getInstance(project); + Map map = new HashMap<>(); + collect(map, instance.getTranslations().getNodes(), null, previewLocale, prefix); + Map containedPath = new HashMap<>(); + StringBuilder prefixedKey = new StringBuilder(); + while (containedPath.isEmpty()) { + for (Map.Entry e : map.entrySet()) { + if (e.getKey().startsWith(path)) { + containedPath.put(e.getKey(), e.getValue()); + } } + if (path.isEmpty()) break; + if (containedPath.isEmpty()) { + prefixedKey.append(path.charAt(0)); + } + path = path.substring(1); } - - LocalizedNode node = sections.isEmpty() ? DataStore.getInstance(project).getTranslations().getNodes() - : DataStore.getInstance(project).getTranslations().getNode(path); - - if(node == null) { // Unknown translation - return; - } - - append(node, previewLocale, path, prefix, result); -// for(LocalizedNode children : node.getChildren()) { -// if(lastSection == null || children.getKey().startsWith(lastSection)) { -// // Construct full key path / Fore nested objects add '.' to indicate deeper level -// String fullKey = (path.isEmpty() ? children.getKey() : path + "." + children.getKey()) + (children.isLeaf() ? "" : "."); -// -// if (prefix != null && !fullKey.isEmpty()) { -// fullKey = prefix + "." + fullKey; -// } -// -// result.addElement(LookupElementBuilder.create(fullKey) -// .appendTailText(getTailText(children, previewLocale), true)); -// } -// } + containedPath.forEach((key, value) -> { + result.addElement(LookupElementBuilder.create(prefixedKey + key).appendTailText(" I18n("+previewLocale+": "+value+")", true)); + }); } - private void append(LocalizedNode node, String locale, String path, String prefix, CompletionResultSet result) { + private void collect(Map map, LocalizedNode node, String path, String locale, String prefix) { if (node.isLeaf() && !node.getKey().equals(LocalizedNode.ROOT_KEY)) { + String value = node.getValue().get(locale); + map.put(path, value); if (prefix != null) { - path = prefix + "." + path; + map.put(prefix + "." + path, value); } - result.addElement(LookupElementBuilder.create(path) - .appendTailText(getTailText(node, locale), true)); } else { - for (LocalizedNode children : node.getChildren()) { - append(children, locale, path == null || path.isEmpty() ? children.getKey() : path + "." + children.getKey(), prefix, result); + for (LocalizedNode child : node.getChildren()) { + collect(map, child, path == null || path.isEmpty() ? child.getKey() : path + "." + child.getKey(), locale, prefix); } } } - private String getTailText(LocalizedNode node, String previewLocale) { - return !node.isLeaf() ? " I18n([])" - : " I18n(" + previewLocale + ": " + node.getValue().get(previewLocale) + ")"; - } } diff --git a/src/main/java/de/marhali/easyi18n/editor/generic/GenericKeyCompletionContributor.java b/src/main/java/de/marhali/easyi18n/editor/generic/GenericKeyCompletionContributor.java index e84b7ec..a8277d3 100644 --- a/src/main/java/de/marhali/easyi18n/editor/generic/GenericKeyCompletionContributor.java +++ b/src/main/java/de/marhali/easyi18n/editor/generic/GenericKeyCompletionContributor.java @@ -13,7 +13,8 @@ import de.marhali.easyi18n.editor.KeyCompletionProvider; public class GenericKeyCompletionContributor extends CompletionContributor { public GenericKeyCompletionContributor() { - extend(CompletionType.BASIC, PlatformPatterns.psiElement().inside(PsiLiteralValue.class), - new KeyCompletionProvider()); +// extend(CompletionType.BASIC, PlatformPatterns.psiElement().inside(PsiLiteralValue.class), +// new KeyCompletionProvider()); + extend(CompletionType.BASIC, PlatformPatterns.psiElement(), new KeyCompletionProvider()); } } \ No newline at end of file From d82206480ecb60ad0fb920d9c53a2f0472875761 Mon Sep 17 00:00:00 2001 From: sunarya-thito Date: Tue, 14 Sep 2021 19:05:04 +0700 Subject: [PATCH 3/6] Fixed non-prefix path --- .../java/de/marhali/easyi18n/editor/KeyCompletionProvider.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/de/marhali/easyi18n/editor/KeyCompletionProvider.java b/src/main/java/de/marhali/easyi18n/editor/KeyCompletionProvider.java index 0092851..1014da9 100644 --- a/src/main/java/de/marhali/easyi18n/editor/KeyCompletionProvider.java +++ b/src/main/java/de/marhali/easyi18n/editor/KeyCompletionProvider.java @@ -62,7 +62,7 @@ public class KeyCompletionProvider extends CompletionProvider Date: Tue, 14 Sep 2021 19:11:15 +0700 Subject: [PATCH 4/6] Fixed dot suffix --- .../de/marhali/easyi18n/editor/KeyCompletionProvider.java | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/main/java/de/marhali/easyi18n/editor/KeyCompletionProvider.java b/src/main/java/de/marhali/easyi18n/editor/KeyCompletionProvider.java index 1014da9..b3bef94 100644 --- a/src/main/java/de/marhali/easyi18n/editor/KeyCompletionProvider.java +++ b/src/main/java/de/marhali/easyi18n/editor/KeyCompletionProvider.java @@ -32,10 +32,6 @@ public class KeyCompletionProvider extends CompletionProvider map = new HashMap<>(); collect(map, instance.getTranslations().getNodes(), null, previewLocale, prefix); From 0fc8f36fe94f6101d63d2754185266d3a196d00a Mon Sep 17 00:00:00 2001 From: sunarya-thito Date: Tue, 14 Sep 2021 22:18:22 +0700 Subject: [PATCH 5/6] Limit prefix length --- .../java/de/marhali/easyi18n/editor/KeyCompletionProvider.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/de/marhali/easyi18n/editor/KeyCompletionProvider.java b/src/main/java/de/marhali/easyi18n/editor/KeyCompletionProvider.java index b3bef94..c5039d9 100644 --- a/src/main/java/de/marhali/easyi18n/editor/KeyCompletionProvider.java +++ b/src/main/java/de/marhali/easyi18n/editor/KeyCompletionProvider.java @@ -37,7 +37,8 @@ public class KeyCompletionProvider extends CompletionProvider containedPath = new HashMap<>(); StringBuilder prefixedKey = new StringBuilder(); - while (containedPath.isEmpty()) { + int maxPrefixLookUpLength = 5; + while (containedPath.isEmpty() && maxPrefixLookUpLength-- > 0) { for (Map.Entry e : map.entrySet()) { if (e.getKey().startsWith(path)) { containedPath.put(e.getKey(), e.getValue()); From 35421103832012200ca43f4f788ddd7cdfa1aef7 Mon Sep 17 00:00:00 2001 From: sunarya-thito Date: Tue, 14 Sep 2021 22:48:20 +0700 Subject: [PATCH 6/6] No longer show completion on non-sense places --- .../generic/GenericKeyCompletionContributor.java | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/main/java/de/marhali/easyi18n/editor/generic/GenericKeyCompletionContributor.java b/src/main/java/de/marhali/easyi18n/editor/generic/GenericKeyCompletionContributor.java index a8277d3..e958285 100644 --- a/src/main/java/de/marhali/easyi18n/editor/generic/GenericKeyCompletionContributor.java +++ b/src/main/java/de/marhali/easyi18n/editor/generic/GenericKeyCompletionContributor.java @@ -2,8 +2,10 @@ package de.marhali.easyi18n.editor.generic; import com.intellij.codeInsight.completion.CompletionContributor; import com.intellij.codeInsight.completion.CompletionType; +import com.intellij.lang.*; import com.intellij.patterns.*; -import com.intellij.psi.PsiLiteralValue; +import com.intellij.psi.*; +import com.intellij.psi.xml.*; import de.marhali.easyi18n.editor.KeyCompletionProvider; /** @@ -13,8 +15,11 @@ import de.marhali.easyi18n.editor.KeyCompletionProvider; public class GenericKeyCompletionContributor extends CompletionContributor { public GenericKeyCompletionContributor() { -// extend(CompletionType.BASIC, PlatformPatterns.psiElement().inside(PsiLiteralValue.class), -// new KeyCompletionProvider()); - extend(CompletionType.BASIC, PlatformPatterns.psiElement(), new KeyCompletionProvider()); + extend(CompletionType.BASIC, PlatformPatterns.psiElement(PlainTextTokenTypes.PLAIN_TEXT), + new KeyCompletionProvider()); + extend(CompletionType.BASIC, PlatformPatterns.psiElement().inside(XmlElement.class), + new KeyCompletionProvider()); + extend(CompletionType.BASIC, PlatformPatterns.psiElement().inside(PsiLiteralValue.class), + new KeyCompletionProvider()); } } \ No newline at end of file