From c682953524d601e82d67a84ed2324fe42e31f2e5 Mon Sep 17 00:00:00 2001 From: Michael Billington Date: Sun, 30 Jan 2022 22:13:55 +1100 Subject: [PATCH] add additional intentions for converting literals to binary and decimal conversions --- build.gradle | 2 +- .../ConvertNumberToBinaryIntentionAction.java | 76 +++++++++++++++++++ ...ConvertNumberToDecimalIntentionAction.java | 67 ++++++++++++++++ ...ertNumberToHexadecimalIntentionAction.java | 30 ++------ .../org/ca65/action/IntentionActionUtil.java | 44 +++++++++++ src/main/resources/META-INF/plugin.xml | 10 +++ .../after.s.template | 1 + .../before.s.template | 1 + .../description.html | 7 ++ .../after.s.template | 1 + .../before.s.template | 1 + .../description.html | 7 ++ .../description.html | 2 +- .../messages/Asm6502Bundle.properties | 6 ++ 14 files changed, 228 insertions(+), 27 deletions(-) create mode 100644 src/main/java/org/ca65/action/ConvertNumberToBinaryIntentionAction.java create mode 100644 src/main/java/org/ca65/action/ConvertNumberToDecimalIntentionAction.java create mode 100644 src/main/java/org/ca65/action/IntentionActionUtil.java create mode 100644 src/main/resources/intentionDescriptions/ConvertNumberToBinaryIntentionAction/after.s.template create mode 100644 src/main/resources/intentionDescriptions/ConvertNumberToBinaryIntentionAction/before.s.template create mode 100644 src/main/resources/intentionDescriptions/ConvertNumberToBinaryIntentionAction/description.html create mode 100644 src/main/resources/intentionDescriptions/ConvertNumberToDecimalIntentionAction/after.s.template create mode 100644 src/main/resources/intentionDescriptions/ConvertNumberToDecimalIntentionAction/before.s.template create mode 100644 src/main/resources/intentionDescriptions/ConvertNumberToDecimalIntentionAction/description.html diff --git a/build.gradle b/build.gradle index bfb2eeb..fce6686 100644 --- a/build.gradle +++ b/build.gradle @@ -38,7 +38,7 @@ runPluginVerifier { } patchPluginXml { - changeNotes = """This change adds an intention action for converting numeric literals to hexadecimal.""" + changeNotes = """This change adds intention actions for converting numeric literals between hexadecimal, decimal and binary representations.""" sinceBuild = '211.6693' } diff --git a/src/main/java/org/ca65/action/ConvertNumberToBinaryIntentionAction.java b/src/main/java/org/ca65/action/ConvertNumberToBinaryIntentionAction.java new file mode 100644 index 0000000..12f9476 --- /dev/null +++ b/src/main/java/org/ca65/action/ConvertNumberToBinaryIntentionAction.java @@ -0,0 +1,76 @@ +package org.ca65.action; + +import com.intellij.codeInsight.intention.impl.BaseIntentionAction; +import com.intellij.codeInspection.util.IntentionFamilyName; +import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.project.Project; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiFile; +import com.intellij.util.IncorrectOperationException; +import org.apache.commons.lang.StringUtils; +import org.ca65.Asm6502Bundle; +import org.ca65.psi.AsmElementFactory; +import org.ca65.psi.AsmFile; +import org.ca65.psi.AsmNumericLiteral; +import org.jetbrains.annotations.NotNull; + +import static org.ca65.action.IntentionActionUtil.*; + +public class ConvertNumberToBinaryIntentionAction extends BaseIntentionAction { + @Override + public @NotNull @IntentionFamilyName String getFamilyName() { + return Asm6502Bundle.message("INTN.NAME.convert.to.bin"); + } + + @Override + public boolean isAvailable(@NotNull Project project, Editor editor, PsiFile file) { + if (!(file instanceof AsmFile)) { + return false; + } + AsmNumericLiteral literal = getAsmNumericLiteral(editor, file); + if (literal == null) { // Caret is not over a numeric literal + return false; + } + String text = literal.getText(); + if (!canConvertToBinary(text)) { + return false; + } + setText(Asm6502Bundle.message("INTN.convert.to.bin", literal.getText())); + return true; + } + + private static boolean canConvertToBinary(String text) { + return isConvertibleDec(text) || isConvertibleHex(text); + } + + private static String doConvertToBinary(String str) { + // Parse + final int intValue; + if(str.startsWith("$")) { + intValue = Integer.parseInt(str.substring(1), 16); // From hex eg. "$ff" + } else { + intValue = Integer.parseInt(str, 10); // From dec eg. "42" + } + String binString = Integer.toBinaryString(intValue); + // Pad to 8 bit, 16 bit, 24-bit values. + int currentLen = binString.length(); + int remainder = currentLen % 8; + if(remainder != 0) { + // Pad to multiple of 8 bits + binString = StringUtils.leftPad(binString, (currentLen + 8) - remainder, "0"); + } + return "%" + binString; // Prefixed with % + } + + @Override + public void invoke(@NotNull Project project, Editor editor, PsiFile file) throws IncorrectOperationException { + AsmNumericLiteral literal = getAsmNumericLiteral(editor, file); + if(literal == null || !canConvertToBinary(literal.getText())) { + // Some weirdness if this happens + return; + } + String replacement = doConvertToBinary(literal.getText()); + PsiElement newLiteral = AsmElementFactory.createNumericLiteral(project, replacement); + literal.replace(newLiteral); + } +} diff --git a/src/main/java/org/ca65/action/ConvertNumberToDecimalIntentionAction.java b/src/main/java/org/ca65/action/ConvertNumberToDecimalIntentionAction.java new file mode 100644 index 0000000..a977a60 --- /dev/null +++ b/src/main/java/org/ca65/action/ConvertNumberToDecimalIntentionAction.java @@ -0,0 +1,67 @@ +package org.ca65.action; + +import com.intellij.codeInsight.intention.impl.BaseIntentionAction; +import com.intellij.codeInspection.util.IntentionFamilyName; +import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.project.Project; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiFile; +import com.intellij.util.IncorrectOperationException; +import org.ca65.Asm6502Bundle; +import org.ca65.psi.AsmElementFactory; +import org.ca65.psi.AsmFile; +import org.ca65.psi.AsmNumericLiteral; +import org.jetbrains.annotations.NotNull; + +import static org.ca65.action.IntentionActionUtil.*; + +public class ConvertNumberToDecimalIntentionAction extends BaseIntentionAction { + @Override + public @NotNull @IntentionFamilyName String getFamilyName() { + return Asm6502Bundle.message("INTN.NAME.convert.to.dec"); + } + + @Override + public boolean isAvailable(@NotNull Project project, Editor editor, PsiFile file) { + if (!(file instanceof AsmFile)) { + return false; + } + AsmNumericLiteral literal = getAsmNumericLiteral(editor, file); + if (literal == null) { // Caret is not over a numeric literal + return false; + } + String text = literal.getText(); + if (!canConvertToDecimal(text)) { + return false; + } + setText(Asm6502Bundle.message("INTN.convert.to.dec", literal.getText())); + return true; + } + + private static boolean canConvertToDecimal(String text) { + return isConvertibleBin(text) || isConvertibleHex(text); + } + + private static String doConvertToDecimal(String str) { + // Parse + final int intValue; + if(str.startsWith("%")) { + intValue = Integer.parseInt(str.substring(1), 2); // From bin eg. "%01010" + } else { + intValue = Integer.parseInt(str.substring(1), 16); // From hex eg. "$ff" + } + return Integer.toString(intValue); + } + + @Override + public void invoke(@NotNull Project project, Editor editor, PsiFile file) throws IncorrectOperationException { + AsmNumericLiteral literal = getAsmNumericLiteral(editor, file); + if(literal == null || !canConvertToDecimal(literal.getText())) { + // Some weirdness if this happens + return; + } + String replacement = doConvertToDecimal(literal.getText()); + PsiElement newLiteral = AsmElementFactory.createNumericLiteral(project, replacement); + literal.replace(newLiteral); + } +} diff --git a/src/main/java/org/ca65/action/ConvertNumberToHexadecimalIntentionAction.java b/src/main/java/org/ca65/action/ConvertNumberToHexadecimalIntentionAction.java index a99f6f1..9835626 100644 --- a/src/main/java/org/ca65/action/ConvertNumberToHexadecimalIntentionAction.java +++ b/src/main/java/org/ca65/action/ConvertNumberToHexadecimalIntentionAction.java @@ -1,20 +1,19 @@ package org.ca65.action; -import com.intellij.codeInsight.TargetElementUtilBase; import com.intellij.codeInsight.intention.impl.BaseIntentionAction; import com.intellij.codeInspection.util.IntentionFamilyName; import com.intellij.openapi.editor.Editor; import com.intellij.openapi.project.Project; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiFile; -import com.intellij.psi.util.PsiTreeUtil; import com.intellij.util.IncorrectOperationException; import org.ca65.Asm6502Bundle; import org.ca65.psi.AsmElementFactory; import org.ca65.psi.AsmFile; import org.ca65.psi.AsmNumericLiteral; import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; + +import static org.ca65.action.IntentionActionUtil.*; public class ConvertNumberToHexadecimalIntentionAction extends BaseIntentionAction { @Override @@ -39,36 +38,17 @@ public boolean isAvailable(@NotNull Project project, Editor editor, PsiFile file return true; } - @Nullable - private AsmNumericLiteral getAsmNumericLiteral(Editor editor, PsiFile file) { - final int offset = TargetElementUtilBase.adjustOffset(file, editor.getDocument(), editor.getCaretModel().getOffset()); - return PsiTreeUtil.getParentOfType(file.findElementAt(offset), AsmNumericLiteral.class); - } - private static boolean canConvertToHex(String str) { - if(str.startsWith("$")) { // Already in hex - return false; - } - if(str.startsWith("%")) { - // In binary, just do length check for 24 bits max. - return str.length() > 1 && str.length() <= 25; - } - // Decimal literal. Check length first to avoid overflow weirdness - int maxValue = 16777215; - if(str.length() > Integer.toString(maxValue).length()) { - return false; - } - int parsedValue = Integer.parseInt(str); - return parsedValue >= 0 && parsedValue <= maxValue; + return isConvertibleDec(str) || isConvertibleBin(str); } private static String doConvertToHex(String str) { // Parse final int intValue; if(str.startsWith("%")) { - intValue = Integer.parseInt(str.substring(1), 2); + intValue = Integer.parseInt(str.substring(1), 2);; // From bin eg. "%01010" } else { - intValue = Integer.parseInt(str, 10); + intValue = Integer.parseInt(str, 10); // From dec eg. "42" } String rawHexString = Integer.toHexString(intValue); if(rawHexString.length() % 2 == 1) { // Even number of digits. Expect 8 bit, 16 bit, 24-bit values. diff --git a/src/main/java/org/ca65/action/IntentionActionUtil.java b/src/main/java/org/ca65/action/IntentionActionUtil.java new file mode 100644 index 0000000..df75960 --- /dev/null +++ b/src/main/java/org/ca65/action/IntentionActionUtil.java @@ -0,0 +1,44 @@ +package org.ca65.action; + +import com.intellij.codeInsight.TargetElementUtilBase; +import com.intellij.openapi.editor.Editor; +import com.intellij.psi.PsiFile; +import com.intellij.psi.util.PsiTreeUtil; +import org.ca65.psi.AsmNumericLiteral; +import org.jetbrains.annotations.Nullable; + +public class IntentionActionUtil { + @Nullable + public static AsmNumericLiteral getAsmNumericLiteral(Editor editor, PsiFile file) { + final int offset = TargetElementUtilBase.adjustOffset(file, editor.getDocument(), editor.getCaretModel().getOffset()); + return PsiTreeUtil.getParentOfType(file.findElementAt(offset), AsmNumericLiteral.class); + } + + public static boolean isConvertibleHex(String text) { + if(!text.startsWith("$")) { // Already in hex + return false; + } + return text.length() > 1 && text.length() <= 7; + } + + public static boolean isConvertibleBin(String text) { + if(!text.startsWith("%")) { + return false; + } + // In binary, just do length check for 24 bits max. + return text.length() > 1 && text.length() <= 25; + } + + public static boolean isConvertibleDec(String text) { + if(text.startsWith("%") || text.startsWith("$")) { + return false; + } + // Decimal literal. Check length first to avoid overflow weirdness + int maxValue = 16777215; + if(text.length() > Integer.toString(maxValue).length()) { + return false; + } + int parsedValue = Integer.parseInt(text); + return parsedValue >= 0 && parsedValue <= maxValue; + } +} diff --git a/src/main/resources/META-INF/plugin.xml b/src/main/resources/META-INF/plugin.xml index e082c7a..fb66cf5 100644 --- a/src/main/resources/META-INF/plugin.xml +++ b/src/main/resources/META-INF/plugin.xml @@ -49,6 +49,16 @@ messages.Asm6502Bundle INTN.category.asm6502 + + org.ca65.action.ConvertNumberToDecimalIntentionAction + messages.Asm6502Bundle + INTN.category.asm6502 + + + org.ca65.action.ConvertNumberToBinaryIntentionAction + messages.Asm6502Bundle + INTN.category.asm6502 + diff --git a/src/main/resources/intentionDescriptions/ConvertNumberToBinaryIntentionAction/after.s.template b/src/main/resources/intentionDescriptions/ConvertNumberToBinaryIntentionAction/after.s.template new file mode 100644 index 0000000..02a8374 --- /dev/null +++ b/src/main/resources/intentionDescriptions/ConvertNumberToBinaryIntentionAction/after.s.template @@ -0,0 +1 @@ +lda #%00101010 diff --git a/src/main/resources/intentionDescriptions/ConvertNumberToBinaryIntentionAction/before.s.template b/src/main/resources/intentionDescriptions/ConvertNumberToBinaryIntentionAction/before.s.template new file mode 100644 index 0000000..22f6eaf --- /dev/null +++ b/src/main/resources/intentionDescriptions/ConvertNumberToBinaryIntentionAction/before.s.template @@ -0,0 +1 @@ +lda #42 diff --git a/src/main/resources/intentionDescriptions/ConvertNumberToBinaryIntentionAction/description.html b/src/main/resources/intentionDescriptions/ConvertNumberToBinaryIntentionAction/description.html new file mode 100644 index 0000000..86b6373 --- /dev/null +++ b/src/main/resources/intentionDescriptions/ConvertNumberToBinaryIntentionAction/description.html @@ -0,0 +1,7 @@ + + +Replaces a numeric literal (decimal or hexadecimal) with an equivalent binary value. + +This intention is available for decimal or hexadecimal literals of up to 24 bits. The result will be represented as an 8, 16, or 24 digit binary number, padded with '0' if necessary. + + diff --git a/src/main/resources/intentionDescriptions/ConvertNumberToDecimalIntentionAction/after.s.template b/src/main/resources/intentionDescriptions/ConvertNumberToDecimalIntentionAction/after.s.template new file mode 100644 index 0000000..ab15812 --- /dev/null +++ b/src/main/resources/intentionDescriptions/ConvertNumberToDecimalIntentionAction/after.s.template @@ -0,0 +1 @@ +sta $5fff diff --git a/src/main/resources/intentionDescriptions/ConvertNumberToDecimalIntentionAction/before.s.template b/src/main/resources/intentionDescriptions/ConvertNumberToDecimalIntentionAction/before.s.template new file mode 100644 index 0000000..232efa1 --- /dev/null +++ b/src/main/resources/intentionDescriptions/ConvertNumberToDecimalIntentionAction/before.s.template @@ -0,0 +1 @@ +sta 24575 diff --git a/src/main/resources/intentionDescriptions/ConvertNumberToDecimalIntentionAction/description.html b/src/main/resources/intentionDescriptions/ConvertNumberToDecimalIntentionAction/description.html new file mode 100644 index 0000000..ddd6d03 --- /dev/null +++ b/src/main/resources/intentionDescriptions/ConvertNumberToDecimalIntentionAction/description.html @@ -0,0 +1,7 @@ + + +Replaces a numeric literal (hexadecimal or binary) with an equivalent decimal value. + +This intention is available for hexadecimal or binary literals of up to 24 bits. + + diff --git a/src/main/resources/intentionDescriptions/ConvertNumberToHexadecimalIntentionAction/description.html b/src/main/resources/intentionDescriptions/ConvertNumberToHexadecimalIntentionAction/description.html index a2387dc..96854f1 100644 --- a/src/main/resources/intentionDescriptions/ConvertNumberToHexadecimalIntentionAction/description.html +++ b/src/main/resources/intentionDescriptions/ConvertNumberToHexadecimalIntentionAction/description.html @@ -2,6 +2,6 @@ Replaces a numeric literal (decimal or binary) with an equivalent hexadecimal value. -This intention is available for decimal or binary literals of up to 24 bits. The result will be represented as a two, four, or six digit hexadecimal number, prefixed with '0' if necessary. +This intention is available for decimal or binary literals of up to 24 bits. The result will be represented as a 2, 4, or 6 digit hexadecimal number, padded with '0' if necessary. diff --git a/src/main/resources/messages/Asm6502Bundle.properties b/src/main/resources/messages/Asm6502Bundle.properties index d90bcf9..eda165a 100644 --- a/src/main/resources/messages/Asm6502Bundle.properties +++ b/src/main/resources/messages/Asm6502Bundle.properties @@ -2,3 +2,9 @@ INTN.category.asm6502=6502 assembly INTN.NAME.convert.to.hex=Convert to hexadecimal INTN.convert.to.hex=Convert ''{0}'' to hexadecimal + +INTN.NAME.convert.to.dec=Convert to decimal +INTN.convert.to.dec=Convert ''{0}'' to decimal + +INTN.NAME.convert.to.bin=Convert to binary +INTN.convert.to.bin=Convert ''{0}'' to binary