package cn.yiiguxing.plugin.translate.action; import cn.yiiguxing.plugin.translate.Settings; import cn.yiiguxing.plugin.translate.Translator; import cn.yiiguxing.plugin.translate.Utils; import cn.yiiguxing.plugin.translate.compat.SelectWordUtilCompat; import cn.yiiguxing.plugin.translate.model.BasicExplain; import cn.yiiguxing.plugin.translate.model.QueryResult; import com.intellij.codeInsight.highlighting.HighlightManager; import com.intellij.codeInsight.lookup.*; import com.intellij.openapi.actionSystem.AnActionEvent; import com.intellij.openapi.actionSystem.PlatformDataKeys; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.editor.Editor; import com.intellij.openapi.editor.ScrollType; import com.intellij.openapi.editor.SelectionModel; import com.intellij.openapi.editor.markup.EffectType; import com.intellij.openapi.editor.markup.RangeHighlighter; import com.intellij.openapi.editor.markup.TextAttributes; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.TextRange; import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.vfs.ReadonlyStatusHandler; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.ui.JBColor; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.*; /** * 翻译并替换 */ public class TranslateAndReplaceAction extends AutoSelectAction { private static final String PATTERN_FIX = "^(\\[[\\u4E00-\\u9FBF]+])+ "; private static final TextAttributes HIGHLIGHT_ATTRIBUTES; static { TextAttributes attributes = new TextAttributes(); attributes.setEffectType(EffectType.BOXED); attributes.setEffectColor(new JBColor(0xFFFF0000, 0xFFFF0000)); HIGHLIGHT_ATTRIBUTES = attributes; } private final Settings mSettings; public TranslateAndReplaceAction() { super(SelectWordUtilCompat.HANZI_CONDITION, true); setEnabledInModalContext(false); mSettings = Settings.getInstance(); } @NotNull @Override protected AutoSelectionMode getAutoSelectionMode() { return mSettings.getAutoSelectionMode(); } @Override protected void onUpdate(AnActionEvent e, boolean active) { if (active) { final Editor editor = getEditor(e); if (editor != null) { SelectionModel selectionModel = editor.getSelectionModel(); if (selectionModel.hasSelection()) { final String selectedText = selectionModel.getSelectedText(); if (!Utils.isEmptyOrBlankString(selectedText)) { boolean hasHanzi = false; for (int i = 0; i < selectedText.length(); i++) { if (SelectWordUtilCompat.HANZI_CONDITION.value(selectedText.charAt(i))) { hasHanzi = true; break; } } active = hasHanzi; } } } } e.getPresentation().setEnabledAndVisible(active); } @Override protected void onActionPerformed(final AnActionEvent e, @NotNull final Editor editor, @NotNull final TextRange selectionRange) { VirtualFile virtualFile = e.getData(PlatformDataKeys.VIRTUAL_FILE); final Project project = e.getProject(); if (project == null || (virtualFile != null && ReadonlyStatusHandler.getInstance(project).ensureFilesWritable(virtualFile).hasReadonlyFiles())) { return; } final String text = editor.getDocument().getText(selectionRange); if (Utils.isEmptyOrBlankString(text)) return; Translator.getInstance().query(text, new Translator.Callback() { @Override public void onQuery(@Nullable String query, @Nullable final QueryResult result) { if (result == null || result.getErrorCode() != QueryResult.ERROR_CODE_NONE) return; final List<LookupElement> replaceLookup = getReplaceLookupElements(result); if (replaceLookup.isEmpty()) return; ApplicationManager.getApplication().invokeLater(new Runnable() { @Override public void run() { doReplace(editor, selectionRange, text, replaceLookup); } }); } }); } private static void doReplace(@NotNull final Editor editor, @NotNull final TextRange selectionRange, @NotNull final String targetText, @NotNull final List<LookupElement> replaceLookup) { if (editor.isDisposed() || editor.getProject() == null || !targetText.equals(editor.getDocument().getText(selectionRange)) || !selectionRange.containsOffset(editor.getCaretModel().getOffset())) { return; } final SelectionModel selectionModel = editor.getSelectionModel(); final int startOffset = selectionRange.getStartOffset(); final int endOffset = selectionRange.getEndOffset(); if (selectionModel.hasSelection()) { if (selectionModel.getSelectionStart() != startOffset || selectionModel.getSelectionEnd() != endOffset) { return; } } else { selectionModel.setSelection(startOffset, endOffset); } editor.getScrollingModel().scrollToCaret(ScrollType.MAKE_VISIBLE); editor.getCaretModel().moveToOffset(endOffset); LookupElement[] items = replaceLookup.toArray(new LookupElement[replaceLookup.size()]); final LookupEx lookup = LookupManager.getInstance(editor.getProject()).showLookup(editor, items); if (lookup == null) { return; } final HighlightManager highlightManager = HighlightManager.getInstance(editor.getProject()); final List<RangeHighlighter> highlighters = addHighlight(highlightManager, editor, selectionRange); lookup.addLookupListener(new LookupAdapter() { @Override public void itemSelected(LookupEvent event) { disposeHighlight(highlighters); } @Override public void lookupCanceled(LookupEvent event) { selectionModel.removeSelection(); disposeHighlight(highlighters); } }); } @NotNull private static List<RangeHighlighter> addHighlight(@NotNull HighlightManager highlightManager, @NotNull Editor editor, @NotNull TextRange selectionRange) { final ArrayList<RangeHighlighter> highlighters = new ArrayList<RangeHighlighter>(); highlightManager.addOccurrenceHighlight(editor, selectionRange.getStartOffset(), selectionRange.getEndOffset(), HIGHLIGHT_ATTRIBUTES, 0, highlighters, null); for (RangeHighlighter highlighter : highlighters) { highlighter.setGreedyToLeft(true); highlighter.setGreedyToRight(true); } return highlighters; } private static void disposeHighlight(@NotNull List<RangeHighlighter> highlighters) { for (RangeHighlighter highlighter : highlighters) { highlighter.dispose(); } } @NotNull private static List<LookupElement> getReplaceLookupElements(@NotNull QueryResult result) { final List<LookupElement> replaceLookup; BasicExplain basicExplain = result.getBasicExplain(); if (basicExplain != null) { replaceLookup = getReplaceLookupElements(Utils.expandExplain(basicExplain.getExplains())); } else { replaceLookup = getReplaceLookupElements(result.getTranslation()); } return replaceLookup; } @NotNull private static List<LookupElement> getReplaceLookupElements(@Nullable String[] explains) { if (explains == null || explains.length == 0) return Collections.emptyList(); final Set<LookupElement> camel = new LinkedHashSet<LookupElement>(); final Set<LookupElement> pascal = new LinkedHashSet<LookupElement>(); final Set<LookupElement> lowerWithUnder = new LinkedHashSet<LookupElement>(); final Set<LookupElement> capsWithUnder = new LinkedHashSet<LookupElement>(); final Set<LookupElement> withSpace = new LinkedHashSet<LookupElement>(); final StringBuilder camelBuilder = new StringBuilder(); final StringBuilder pascalBuilder = new StringBuilder(); final StringBuilder lowerWithUnderBuilder = new StringBuilder(); final StringBuilder capsWithUnderBuilder = new StringBuilder(); final StringBuilder withSpaceBuilder = new StringBuilder(); for (String explain : explains) { List<String> words = fixAndSplitForVariable(explain); if (words == null || words.isEmpty()) { continue; } camelBuilder.setLength(0); pascalBuilder.setLength(0); lowerWithUnderBuilder.setLength(0); capsWithUnderBuilder.setLength(0); withSpaceBuilder.setLength(0); build(words, camelBuilder, pascalBuilder, lowerWithUnderBuilder, capsWithUnderBuilder, withSpaceBuilder); camel.add(LookupElementBuilder.create(camelBuilder.toString())); pascal.add(LookupElementBuilder.create(pascalBuilder.toString())); lowerWithUnder.add(LookupElementBuilder.create(lowerWithUnderBuilder.toString())); capsWithUnder.add(LookupElementBuilder.create(capsWithUnderBuilder.toString())); withSpace.add(LookupElementBuilder.create(withSpaceBuilder.toString())); } final Set<LookupElement> result = new LinkedHashSet<LookupElement>(); result.addAll(camel); result.addAll(pascal); result.addAll(lowerWithUnder); result.addAll(capsWithUnder); result.addAll(withSpace); return Collections.unmodifiableList(new ArrayList<LookupElement>(result)); } private static void build(@NotNull final List<String> words, @NotNull final StringBuilder camel, @NotNull final StringBuilder pascal, @NotNull final StringBuilder lowerWithUnder, @NotNull final StringBuilder capsWithUnder, @NotNull final StringBuilder withSpace) { for (int i = 0; i < words.size(); i++) { String word = words.get(i); if (i > 0) { lowerWithUnder.append('_'); capsWithUnder.append('_'); withSpace.append(' '); } withSpace.append(word); if (i == 0) { word = sanitizeJavaIdentifierStart(word); } String capitalized = StringUtil.capitalizeWithJavaBeanConvention(word); String lowerCase = word.toLowerCase(); camel.append(i == 0 ? lowerCase : capitalized); pascal.append(capitalized); lowerWithUnder.append(lowerCase); capsWithUnder.append(word.toUpperCase()); } } @Nullable private static List<String> fixAndSplitForVariable(@NotNull String explains) { String explain = Utils.splitExplain(explains)[1]; if (Utils.isEmptyOrBlankString(explain)) { return null; } String fixed = explain.replaceFirst(PATTERN_FIX, ""); return StringUtil.getWordsIn(fixed); } private static String sanitizeJavaIdentifierStart(@NotNull String name) { return Character.isJavaIdentifierStart(name.charAt(0)) ? name : "_" + name; } }