diff --git a/.gitignore b/.gitignore index 4b7b31c..ab898d0 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ .kotlin notes.txt build +.DS_STORE diff --git a/CHANGELOG.md b/CHANGELOG.md index a18c8f8..d641dc3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,12 +4,37 @@ ## [Unreleased] +## [0.5.0] + +### Added +- Detects messages split over multiple lines +- Add a new column to display field type H for header, M for message, T for trailer + + +## [0.4.0] + +### Added +- Mouse hovering over a fix message will display the fix message details + +## [0.3.0] + +### Added +- Cmd click for fields and groups to go to the field definition +- Mouse hovering over a field or group name will display the field definition +- Add Inspection to replace component reference with the full component definition + +## [0.2.0] + +### Added +- Add possibility to ctrl/cmd + click on the component name to navigate to the definition +- Mouse hovering over a component name will display the component definition + +## [0.1.0] + +### Added +- Add type next to the field name + +## [0.0.1] + ### Added -- v0.0.1 Add tag number next to the field name -- v0.1.0 Add type next to the field name -- v0.2.0 Add possibility to ctrl/cmd + click on the component name to navigate to the definition -- v0.2.0 Mouse hovering over a component name will display the component definition -- v0.3.0 Cmd click for fields and groups to go to the field definition -- v0.3.0 Mouse hovering over a field or group name will display the field definition -- v0.3.0 Add Inspection to replace component reference with the full component definition -- v0.4.0 Mouse hovering over a fix message will display the fix message details +- add tag number next to the field name diff --git a/gradle.properties b/gradle.properties index 83aa053..d9ebd23 100644 --- a/gradle.properties +++ b/gradle.properties @@ -4,7 +4,7 @@ pluginGroup = ac.quant.quickfixspec pluginName = quickfix-spec pluginRepositoryUrl = https://github.com/dantimofte/quickfix-spec # SemVer format -> https://semver.org -pluginVersion = 0.4.0 +pluginVersion = 0.5.0 # Supported build number ranges and IntelliJ Platform versions -> https://plugins.jetbrains.com/docs/intellij/build-number-ranges.html pluginSinceBuild = 233 diff --git a/src/main/kotlin/ac/quant/quickfixspec/common/FixDataDictionaryService.kt b/src/main/kotlin/ac/quant/quickfixspec/common/FixDataDictionaryService.kt deleted file mode 100644 index 1a8c70c..0000000 --- a/src/main/kotlin/ac/quant/quickfixspec/common/FixDataDictionaryService.kt +++ /dev/null @@ -1,45 +0,0 @@ -package ac.quant.quickfixspec.common - -import com.intellij.lang.xml.XMLLanguage -import com.intellij.openapi.project.Project -import com.intellij.openapi.components.Service -import com.intellij.psi.PsiFile -import com.intellij.psi.PsiFileFactory -import com.intellij.psi.xml.XmlFile -import java.net.URL - -@Service(Service. Level. PROJECT) -class FixDataDictionaryService(private val project: Project) { - - private var fields: FixFields? = null - - init { - loadFixSpecs() - } - - private fun loadFixSpecs() { - val filePath: URL? = this::class.java.getResource("/spec/FIX44.xml") - - val fixSpecs : PsiFile? = PsiFileFactory.getInstance(project).createFileFromText( - "FIX40.xml", - XMLLanguage.INSTANCE, - filePath!!.readText() - ) - - fields = getFields(fixSpecs as XmlFile) - } - - private fun getFields(file: XmlFile): FixFields { - val fieldsTag = file.rootTag?.findFirstSubTag("fields") - return FixFields(fieldsTag!!) - } - - fun getTagName(tag: String): String { - return fields?.getTagName(tag) ?: "" - } - - fun getTagValueDefinition(tag: String, value: String): String { - return fields?.getTagValueDefinition(tag, value) ?: "" - } - -} diff --git a/src/main/kotlin/ac/quant/quickfixspec/common/FixField.kt b/src/main/kotlin/ac/quant/quickfixspec/common/FixField.kt deleted file mode 100644 index cb5fff2..0000000 --- a/src/main/kotlin/ac/quant/quickfixspec/common/FixField.kt +++ /dev/null @@ -1,44 +0,0 @@ -package ac.quant.quickfixspec.common - -import com.intellij.psi.xml.XmlTag -import lombok.Getter - -@Getter -class FixField(tag: XmlTag) { - val name: String = parseName(tag) - val number: String = parseNumber(tag) - val fixType: String = parseFixType(tag) - val values: Map = parseValues(tag) - - - private fun parseName(tag: XmlTag): String { - return getAttributeValue(tag, "name") - } - - private fun parseNumber(tag: XmlTag): String { - return getAttributeValue(tag, "number") - } - - private fun parseFixType(tag: XmlTag): String { - return getAttributeValue(tag, "type") - } - - private fun parseValues(tag: XmlTag): Map { - val valueTags = tag.findSubTags("value") - val valueTagsDict = mutableMapOf() - for (valueTag in valueTags) { - try { - val enumValue = getAttributeValue(valueTag, "enum") - val description = getAttributeValue(valueTag, "description") - valueTagsDict[enumValue] = description - } catch (e: Exception) { - println("Error processing value tags. Error: ${e.message}") - } - } - return valueTagsDict - } - - private fun getAttributeValue(tag: XmlTag, attributeName: String): String { - return tag.getAttribute(attributeName)?.value ?: "" - } -} diff --git a/src/main/kotlin/ac/quant/quickfixspec/common/FixFields.kt b/src/main/kotlin/ac/quant/quickfixspec/common/FixFields.kt deleted file mode 100644 index e2386a9..0000000 --- a/src/main/kotlin/ac/quant/quickfixspec/common/FixFields.kt +++ /dev/null @@ -1,38 +0,0 @@ -package ac.quant.quickfixspec.common - -import com.intellij.psi.xml.XmlTag - -class FixFields(fieldsTag: XmlTag) { - private val fieldsByNum: Map - private val fieldsByName: Map - - init { - val result = getFields(fieldsTag) - fieldsByNum = result[0] - fieldsByName = result[1] - } - - private fun getFields(fieldsTag: XmlTag): List> { - val mutableNum = mutableMapOf() - val mutableName = mutableMapOf() - try { - for (field in fieldsTag.subTags) { - val fixField = FixField(field) - mutableNum[fixField.number] = fixField - mutableName[fixField.name] = fixField - } - } catch (e: Exception) { - println("Error processing fields. Error: ${e.message}") - } - return listOf(mutableNum, mutableName) - } - - fun getTagName(tagNumber: String): String { - return fieldsByNum[tagNumber]?.name ?: "" - } - - fun getTagValueDefinition(tag: String, value: String): String { - return fieldsByNum[tag]?.values?.get(value) ?: "" - } - -} diff --git a/src/main/kotlin/ac/quant/quickfixspec/common/parsed/ParsedFixMessage.kt b/src/main/kotlin/ac/quant/quickfixspec/common/parsed/ParsedFixMessage.kt new file mode 100644 index 0000000..e02e4bc --- /dev/null +++ b/src/main/kotlin/ac/quant/quickfixspec/common/parsed/ParsedFixMessage.kt @@ -0,0 +1,80 @@ +package ac.quant.quickfixspec.common.parsed + +import ac.quant.quickfixspec.common.spec.FieldElement +import ac.quant.quickfixspec.common.spec.IFixDataDictionaryService + +class ParsedFixMessage(private val message: String, private val delimiter: String, private val fixDataDictionary: IFixDataDictionaryService) { + private lateinit var msgType: String + lateinit var msgName: String + + private val headerFields = mutableListOf() + + init { + parseMessage() + } + + private fun parseMessage() { + val messageParts = message.split(delimiter) + + for (part in messageParts) { + val tagNumber = clean(part.substringBefore("=")) + + if (tagNumber.isEmpty()) { + continue + } + + val tagValue = part.substringAfter("=") + val field = fixDataDictionary.getFieldByNumber(tagNumber) + val fieldWithValue = field.withValue(tagValue) + maybeAddHeaderField(fieldWithValue) + maybeSetMessageTypeAndName(fieldWithValue) + } + } + + private fun maybeAddHeaderField(field: FieldElement) { + if (field.isHeaderField()) { + headerFields.add(field) + } + } + + private fun maybeSetMessageTypeAndName(field: FieldElement) { + if (field.number == "35") { + msgType = field.number + msgName = field.name + } + } + + private fun clean(value: String): String { + var cleanedValue = value.trim() + cleanedValue = cleanedValue.replace("'", "") + cleanedValue = cleanedValue.replace("\"", "") + cleanedValue = cleanedValue.trim() + return cleanedValue + } + + fun getMessageDetails(): String { + val messageParts = message.split(delimiter) + + val formattedMessage = messageParts.subList(0, messageParts.size - 1).joinToString("\n") { + val fieldNumber = clean(it.substringBefore("=")) + val fieldValue = it.substringAfter("=") + val field = fixDataDictionary.getFieldByNumber(fieldNumber) + val fieldValueDefinition = field.values[fieldValue] ?: "" + val msgPart = getMsgParts(field) + "$msgPart$fieldNumber${field.name}$fieldValue$fieldValueDefinition" + } + + val displayText = "$formattedMessage
" + + return displayText + } + + private fun getMsgParts(field: FieldElement): String { + return when (true) { + field.isHeaderField() -> "H" + field.isTrailerField() -> "T" + else -> "M" + } + } + +} \ No newline at end of file diff --git a/src/main/kotlin/ac/quant/quickfixspec/common/spec/ComponentElement.kt b/src/main/kotlin/ac/quant/quickfixspec/common/spec/ComponentElement.kt new file mode 100644 index 0000000..479fa2b --- /dev/null +++ b/src/main/kotlin/ac/quant/quickfixspec/common/spec/ComponentElement.kt @@ -0,0 +1,59 @@ +package ac.quant.quickfixspec.common.spec + +import com.intellij.psi.xml.XmlTag + +class ComponentElement(override val name: String,override val type : ElementType,override val elementTag: XmlTag,override val fixDataDictionary: IFixDataDictionaryService): IElement { + override val number: String = "" + + override val fields: MutableList = mutableListOf() + override val components: MutableMap = mutableMapOf() + override val groups: MutableMap = mutableMapOf() + + private val componentsNames : MutableList = mutableListOf() + private val groupsTags: MutableMap = mutableMapOf() + + init { + parseSubTags(elementTag) + } + + private fun parseSubTags(tag: XmlTag) { + for (subTag in tag.subTags) { + when (subTag.name) { + "field" -> { + val fieldName = subTag.getAttribute("name")?.value ?: "" + val field = fixDataDictionary.fields.valuesByName[fieldName] as FieldElement + fields.add(field) + } + "component" -> { + val componentName = subTag.getAttribute("name")?.value ?: "" + componentsNames.add(componentName) + } + "group" -> { + val groupName = subTag.getAttribute("name")?.value ?: "" + groupsTags[groupName] = subTag + } + else -> { + println("Unprocessed component tag name: ${subTag.name}") + } + } + } + } + + fun setSubComponents() { + for (componentName in componentsNames) { + val component = fixDataDictionary.components.valuesByName[componentName] as ComponentElement + components[componentName] = component + } + } + + override fun parseGroups() { + for (groupName in groupsTags.keys) { + val group = GroupElement(groupName, ElementType.GROUP, groupsTags[groupName]!!, fixDataDictionary) + groups[groupName] = group + } + } + + override fun toString(): String { + return "ComponentElement(name='$name', fields=${fields.size}, components=${components.size}, groups=${groups.size})" + } +} diff --git a/src/main/kotlin/ac/quant/quickfixspec/common/spec/DefaultElement.kt b/src/main/kotlin/ac/quant/quickfixspec/common/spec/DefaultElement.kt new file mode 100644 index 0000000..f4c481a --- /dev/null +++ b/src/main/kotlin/ac/quant/quickfixspec/common/spec/DefaultElement.kt @@ -0,0 +1,9 @@ +package ac.quant.quickfixspec.common.spec + +import com.intellij.psi.xml.XmlTag + +class DefaultElement(override val name: String,override val number:String,override val type : ElementType, override val elementTag: XmlTag, override val fixDataDictionary: IFixDataDictionaryService): IElement { + override val fields: MutableList = mutableListOf() + override val components: MutableMap = mutableMapOf() + override val groups: MutableMap = mutableMapOf() +} diff --git a/src/main/kotlin/ac/quant/quickfixspec/common/spec/ElementType.kt b/src/main/kotlin/ac/quant/quickfixspec/common/spec/ElementType.kt new file mode 100644 index 0000000..fcc0c69 --- /dev/null +++ b/src/main/kotlin/ac/quant/quickfixspec/common/spec/ElementType.kt @@ -0,0 +1,8 @@ +package ac.quant.quickfixspec.common.spec + +enum class ElementType(val xmlContainerName: String) { + FIELD("fields"), + GROUP("groups"), + COMPONENT("components"), + MESSAGE("messages") +} diff --git a/src/main/kotlin/ac/quant/quickfixspec/common/spec/Elements.kt b/src/main/kotlin/ac/quant/quickfixspec/common/spec/Elements.kt new file mode 100644 index 0000000..63396d2 --- /dev/null +++ b/src/main/kotlin/ac/quant/quickfixspec/common/spec/Elements.kt @@ -0,0 +1,79 @@ +package ac.quant.quickfixspec.common.spec + +class Elements(private val elementsType: ElementType,private val fixDataDictionary: IFixDataDictionaryService) { + val valuesByName: Map + val valueByNumber: Map + + init { + when (elementsType) { + ElementType.FIELD -> { + valuesByName = parseElements() + valueByNumber = valuesByName.values.associateBy { it.number } + } + ElementType.COMPONENT -> { + valuesByName = parseElements() + valueByNumber = mutableMapOf() + } + ElementType.MESSAGE -> { + valuesByName = parseElements() + valueByNumber = mutableMapOf() + } + else -> { + valuesByName = mutableMapOf() + valueByNumber = mutableMapOf() + } + } + } + + /* + This will set the proper references to subComponents + Also parses the groups now so that the groups can set the proper references to subComponents from the first time + */ + fun initSubTags() { + initSubComponents() + parseGroups() + } + + private fun initSubComponents() { + for (component in valuesByName.values) { + if (component !is ComponentElement) { + continue + } + component.setSubComponents() + } + } + + private fun parseGroups() { + for (element in valuesByName.values) { + element.parseGroups() + } + } + + // simplify call to parametrize the type of the element + private fun parseElements(): Map { + val mutableName = mutableMapOf() + try { + val xmlTag = fixDataDictionary.rootTag.findSubTags(elementsType.xmlContainerName) + for (elementTag in xmlTag.first().subTags) { + val elementName = elementTag.getAttribute("name")?.value ?: "" + when (elementTag.name) { + "field" -> { + mutableName[elementName] = FieldElement(elementName, elementsType, elementTag, fixDataDictionary) + } + "component" -> { + mutableName[elementName] = ComponentElement(elementName, elementsType, elementTag, fixDataDictionary) + } + "message" -> { + mutableName[elementName] = MessageElement(elementName, elementsType, elementTag, fixDataDictionary) + } + else -> { + mutableName[elementName] = DefaultElement(elementName, "", elementsType,elementTag, fixDataDictionary) + } + } + } + } catch (e: Exception) { + println("Error processing fields. Error: ${e.message}") + } + return mutableName + } +} diff --git a/src/main/kotlin/ac/quant/quickfixspec/common/spec/FieldElement.kt b/src/main/kotlin/ac/quant/quickfixspec/common/spec/FieldElement.kt new file mode 100644 index 0000000..239a282 --- /dev/null +++ b/src/main/kotlin/ac/quant/quickfixspec/common/spec/FieldElement.kt @@ -0,0 +1,112 @@ +package ac.quant.quickfixspec.common.spec + +import com.intellij.psi.xml.XmlTag +import lombok.Getter + +@Getter +class FieldElement: IElement { + override val name: String + override val type: ElementType + override val number: String + override val elementTag: XmlTag + override val fixDataDictionary: IFixDataDictionaryService + private val fixType: String + val values: Map + val value: String + + override val fields: MutableList = mutableListOf() + override val components: MutableMap = mutableMapOf() + override val groups: MutableMap = mutableMapOf() + + constructor(xmlTag: XmlTag, fixDataDictionary: IFixDataDictionaryService) { + this.name = "" + this.type = ElementType.FIELD + this.number = "" + this.elementTag = xmlTag + this.fixDataDictionary = fixDataDictionary + this.fixType = "" + this.values = mutableMapOf() + this.value = "" + } + + constructor(name: String, elementType : ElementType, elementTag: XmlTag, fixDataDictionary: IFixDataDictionaryService) { + this.name = name + this.type = elementType + this.elementTag = elementTag + this.fixDataDictionary = fixDataDictionary + this.number = parseNumber(elementTag) + this.fixType = parseFixType(elementTag) + this.values = parseValues(elementTag) + this.value = "" + } + + constructor( + name: String, + type: ElementType, + number: String, + elementTag: XmlTag, + fixDataDictionary: IFixDataDictionaryService, + fixType: String, + values: Map, + value: String + ) { + this.name = name + this.type = type + this.number = number + this.elementTag = elementTag + this.fixDataDictionary = fixDataDictionary + this.fixType = fixType + this.values = values + this.value = value + } + + + + private fun parseNumber(tag: XmlTag): String { + return getAttributeValue(tag, "number") + } + + private fun parseFixType(tag: XmlTag): String { + return getAttributeValue(tag, "type") + } + + private fun parseValues(tag: XmlTag): Map { + val valueTags = tag.findSubTags("value") + val valueTagsDict = mutableMapOf() + for (valueTag in valueTags) { + try { + val enumValue = getAttributeValue(valueTag, "enum") + val description = getAttributeValue(valueTag, "description") + valueTagsDict[enumValue] = description + } catch (e: Exception) { + println("Error processing value tags. Error: ${e.message}") + } + } + return valueTagsDict + } + + fun isHeaderField(): Boolean { + return fixDataDictionary.header.containsField(name) + } + + fun isTrailerField(): Boolean { + return fixDataDictionary.trailer.containsField(name) + } + + override fun toString(): String { + return "FieldElement(name='$name', number='$number', values=${values.size})" + } + + fun withValue(value: String): FieldElement { + return FieldElement( + name = name, + type = type, + number = number, + elementTag = elementTag, + fixDataDictionary = fixDataDictionary, + fixType = fixType, + values = values, + value = value + ) + } + } \ No newline at end of file diff --git a/src/main/kotlin/ac/quant/quickfixspec/common/spec/FixDataDictionaryService.kt b/src/main/kotlin/ac/quant/quickfixspec/common/spec/FixDataDictionaryService.kt new file mode 100644 index 0000000..1a84b43 --- /dev/null +++ b/src/main/kotlin/ac/quant/quickfixspec/common/spec/FixDataDictionaryService.kt @@ -0,0 +1,45 @@ +package ac.quant.quickfixspec.common.spec + +import com.intellij.lang.xml.XMLLanguage +import com.intellij.openapi.project.Project +import com.intellij.openapi.components.Service +import com.intellij.psi.PsiFile +import com.intellij.psi.PsiFileFactory +import com.intellij.psi.xml.XmlFile +import com.intellij.psi.xml.XmlTag +import java.net.URL + +@Service(Service. Level. PROJECT) +class FixDataDictionaryService(private val project: Project) : IFixDataDictionaryService { + override val rootTag : XmlTag + override val fields: Elements + override val components: Elements + override val messages: Elements + override val header: MessageElement + override val trailer: MessageElement + + init { + val fixSpecs : PsiFile = loadFixSpecs() + rootTag = (fixSpecs as XmlFile).rootTag!! + fields = Elements(ElementType.FIELD, this) + components = Elements(ElementType.COMPONENT, this) + components.initSubTags() + messages = Elements(ElementType.MESSAGE, this) + messages.initSubTags() + header = MessageElement("header", ElementType.MESSAGE, rootTag.findFirstSubTag("header")!!, this) + trailer = MessageElement("trailer", ElementType.MESSAGE, rootTag.findFirstSubTag("trailer")!!, this) + } + + private fun loadFixSpecs(): PsiFile { + val filePath: URL? = this::class.java.getResource("/spec/FIX44.xml") + + val fixSpecs : PsiFile? = PsiFileFactory.getInstance(project).createFileFromText( + "FIX44.xml", + XMLLanguage.INSTANCE, + filePath!!.readText() + ) + + return fixSpecs!! + } + +} diff --git a/src/main/kotlin/ac/quant/quickfixspec/common/spec/GroupElement.kt b/src/main/kotlin/ac/quant/quickfixspec/common/spec/GroupElement.kt new file mode 100644 index 0000000..df3cbb0 --- /dev/null +++ b/src/main/kotlin/ac/quant/quickfixspec/common/spec/GroupElement.kt @@ -0,0 +1,33 @@ +package ac.quant.quickfixspec.common.spec + +import com.intellij.psi.xml.XmlTag + +class GroupElement(override val name: String,override val type : ElementType,override val elementTag: XmlTag,override val fixDataDictionary: IFixDataDictionaryService): IElement { + override val number: String = fixDataDictionary.fields.valuesByName[name]?.number ?: "" + override val fields: MutableList = mutableListOf() + override val components: MutableMap = mutableMapOf() + override val groups: MutableMap = mutableMapOf() + + init { + parseSubTags() + } + + private fun parseSubTags() { + for (fieldTag in elementTag.subTags) { + when (fieldTag.name) { + "field" -> { + val fieldName = fieldTag.getAttribute("name")?.value ?: "" + val field = fixDataDictionary.fields.valuesByName[fieldName] as FieldElement + fields.add(field) + } + "component" -> { + val componentName = fieldTag.getAttribute("name")?.value ?: "" + components[componentName] = fixDataDictionary.components.valuesByName[componentName] as ComponentElement + } + else -> { + println("Unprocessed group tag name: ${fieldTag.name}") + } + } + } + } +} diff --git a/src/main/kotlin/ac/quant/quickfixspec/common/spec/IElement.kt b/src/main/kotlin/ac/quant/quickfixspec/common/spec/IElement.kt new file mode 100644 index 0000000..4520566 --- /dev/null +++ b/src/main/kotlin/ac/quant/quickfixspec/common/spec/IElement.kt @@ -0,0 +1,24 @@ +package ac.quant.quickfixspec.common.spec + +import com.intellij.psi.xml.XmlTag + +interface IElement { + val name: String + val number: String + val type: ElementType + val elementTag: XmlTag + val fixDataDictionary: IFixDataDictionaryService + + val fields: MutableList + val components: MutableMap + val groups: MutableMap + + + fun getAttributeValue(tag: XmlTag, attributeName: String): String { + return tag.getAttribute(attributeName)?.value ?: "" + } + + fun parseGroups() { + println("Should be parsing groups for element $name , but not doing anything") + } +} diff --git a/src/main/kotlin/ac/quant/quickfixspec/common/spec/IFixDataDictionaryService.kt b/src/main/kotlin/ac/quant/quickfixspec/common/spec/IFixDataDictionaryService.kt new file mode 100644 index 0000000..de8fee8 --- /dev/null +++ b/src/main/kotlin/ac/quant/quickfixspec/common/spec/IFixDataDictionaryService.kt @@ -0,0 +1,17 @@ +package ac.quant.quickfixspec.common.spec + +import com.intellij.psi.xml.XmlTag + +interface IFixDataDictionaryService { + val rootTag : XmlTag + val fields: Elements + val components: Elements + val messages: Elements + val header: MessageElement + val trailer: MessageElement + + fun getFieldByNumber(number: String): FieldElement { + val field = fields.valueByNumber[number] as FieldElement? + return field ?: FieldElement(rootTag, this) + } +} diff --git a/src/main/kotlin/ac/quant/quickfixspec/common/spec/MessageElement.kt b/src/main/kotlin/ac/quant/quickfixspec/common/spec/MessageElement.kt new file mode 100644 index 0000000..0cf7746 --- /dev/null +++ b/src/main/kotlin/ac/quant/quickfixspec/common/spec/MessageElement.kt @@ -0,0 +1,51 @@ +package ac.quant.quickfixspec.common.spec + +import com.intellij.psi.xml.XmlTag + +class MessageElement(override val name: String, override val type : ElementType, override val elementTag: XmlTag, override val fixDataDictionary: IFixDataDictionaryService): IElement { + override val number: String = fixDataDictionary.fields.valuesByName[name]?.number ?: "" + private val msgType : String = elementTag.getAttribute("msgtype")?.value ?: "" + private val msgCat : String = elementTag.getAttribute("msgcat")?.value ?: "" + override val fields: MutableList = mutableListOf() + override val components: MutableMap = mutableMapOf() + override val groups: MutableMap = mutableMapOf() + + init { + parseSubTags() + } + + fun containsField(fieldName: String): Boolean { + return fields.any { it.name == fieldName } + } + + private fun parseSubTags() { + for (fieldTag in elementTag.subTags) { + when (fieldTag.name) { + "field" -> { + val fieldName = fieldTag.getAttribute("name")?.value ?: "" + val field = fixDataDictionary.fields.valuesByName[fieldName] as FieldElement + fields.add(field) + } + "component" -> { + val componentName = fieldTag.getAttribute("name")?.value ?: "" + components[componentName] = fixDataDictionary.components.valuesByName[componentName] as ComponentElement + } + "group" -> { + val groupName = fieldTag.getAttribute("name")?.value ?: "" + groups[groupName] = GroupElement(groupName, ElementType.GROUP, fieldTag, fixDataDictionary) + } + else -> { + val elementNameAttr = elementTag.getAttribute("name")!!.value + val fieldNameAttr = fieldTag.getAttribute("name")!!.value + println("MessageElement with name $elementNameAttr has unprocessed group tag name: ${fieldTag.name} with name $fieldNameAttr") + } + } + } + } + + override fun parseGroups() {} + + override fun toString(): String { + return "MessageElement(name='$name', msgType='$msgType', msgcat='$msgCat', fields=${fields.size}, components=${components.size}, groups=${groups.size})" + } +} diff --git a/src/main/kotlin/ac/quant/quickfixspec/documentation/FixMessageDocumentationTarget.kt b/src/main/kotlin/ac/quant/quickfixspec/documentation/FixMessageDocumentationTarget.kt index 41816bb..cbd28e9 100644 --- a/src/main/kotlin/ac/quant/quickfixspec/documentation/FixMessageDocumentationTarget.kt +++ b/src/main/kotlin/ac/quant/quickfixspec/documentation/FixMessageDocumentationTarget.kt @@ -1,5 +1,5 @@ package ac.quant.quickfixspec.documentation -import ac.quant.quickfixspec.common.FixDataDictionaryService +import ac.quant.quickfixspec.common.parsed.ParsedFixMessage import com.intellij.model.Pointer import com.intellij.platform.backend.documentation.DocumentationResult import com.intellij.platform.backend.documentation.DocumentationTarget @@ -7,18 +7,15 @@ import com.intellij.platform.backend.presentation.TargetPresentation @Suppress("UnstableApiUsage") class FixMessageDocumentationTarget( - private val fixMessage: String, - private val fixDelimiter: String, - private val fixDataDictionaryService: FixDataDictionaryService, + private val fixMessage: ParsedFixMessage, ) : DocumentationTarget { - override fun createPointer(): Pointer { return Pointer.hardPointer(this) } override fun computePresentation(): TargetPresentation { - return TargetPresentation.builder(fixMessage).presentation() + return TargetPresentation.builder(fixMessage.msgName).presentation() } override fun computeDocumentationHint(): String { @@ -27,26 +24,8 @@ class FixMessageDocumentationTarget( } override fun computeDocumentation(): DocumentationResult { - val componentDetails = getMessageDetails() + val componentDetails = fixMessage.getMessageDetails() return DocumentationResult.documentation(componentDetails) } - private fun getMessageDetails(): String { - // split the message by the delimiter - val messageParts = fixMessage.split(fixDelimiter) - // build new string with the tag equals name on each line without the last index - - val formattedMessage = messageParts.subList(0, messageParts.size - 1).joinToString("\n") { - val tagNumber = it.substringBefore("=") - val tagValue = it.substringAfter("=") - val tagName = fixDataDictionaryService.getTagName(tagNumber) - val tagValueDefinition = fixDataDictionaryService.getTagValueDefinition(tagNumber, tagValue) - "$tagNumber$tagName$tagValue$tagValueDefinition" - } - - val displayText = "$formattedMessage
" - - return displayText - } - -} \ No newline at end of file +} diff --git a/src/main/kotlin/ac/quant/quickfixspec/documentation/QuickfixComponentDocumentationTargetProvider.kt b/src/main/kotlin/ac/quant/quickfixspec/documentation/QuickfixComponentDocumentationTargetProvider.kt index 276be3e..50b706d 100644 --- a/src/main/kotlin/ac/quant/quickfixspec/documentation/QuickfixComponentDocumentationTargetProvider.kt +++ b/src/main/kotlin/ac/quant/quickfixspec/documentation/QuickfixComponentDocumentationTargetProvider.kt @@ -1,10 +1,11 @@ package ac.quant.quickfixspec.documentation -import ac.quant.quickfixspec.common.FixDataDictionaryService +import ac.quant.quickfixspec.common.spec.FixDataDictionaryService import ac.quant.quickfixspec.common.PsiUtils.NAME_ATTRIBUTE import ac.quant.quickfixspec.common.PsiUtils.TAGS_WITH_DEFINITION import ac.quant.quickfixspec.common.PsiUtils.DEFINITION_GROUP_NAME import ac.quant.quickfixspec.common.PsiUtils.getRootTag +import ac.quant.quickfixspec.common.parsed.ParsedFixMessage import com.intellij.platform.backend.documentation.DocumentationTarget import com.intellij.platform.backend.documentation.DocumentationTargetProvider import com.intellij.psi.PsiElement @@ -28,7 +29,7 @@ class QuickfixComponentDocumentationTargetProvider : DocumentationTargetProvider return if (file.name.endsWith(".xml")) { getTagDefinition(element) } else { - getFixMessageDetails(file, element) + getFixMessageDetails(file, element, offset) } } @@ -53,8 +54,6 @@ class QuickfixComponentDocumentationTargetProvider : DocumentationTargetProvider val tagParent = tag.parent as? XmlTag ?: return emptyList() - // skip definition when tagParent.name is "components" or "fields" - if (tagParent.name in DEFINITION_GROUP_NAME.values) { return emptyList() } @@ -63,22 +62,27 @@ class QuickfixComponentDocumentationTargetProvider : DocumentationTargetProvider return listOf(QuickfixComponentDocumentationTarget(attrNameValue, tag.name, rootTag)) } - // search for strings that are actually fix messages and return them with the actual name of the tag number based on the xml file - private fun getFixMessageDetails(file: PsiFile, element: PsiElement): List { + private fun getFixMessageDetails(file: PsiFile, element: PsiElement, offset: Int): List { fixDataDictionaryService = file.project.getService(FixDataDictionaryService::class.java) val fixMessages = mutableListOf() - val fixMessageRegex = Regex("8=FIX.*?10=[0-9]{3}(\\\\u0001)?(.)?") - val matches = fixMessageRegex.findAll(element.text) - for (match in matches) { - val fixMessage= match.value - // if group 0 is empty then try group 1 - val fixDelimiter = match.groupValues[1].ifEmpty{ match.groupValues[2] } - fixMessages.add(FixMessageDocumentationTarget(fixMessage, fixDelimiter, fixDataDictionaryService!!)) + val regexOptionsSet = setOf(RegexOption.DOT_MATCHES_ALL, RegexOption.COMMENTS, RegexOption.IGNORE_CASE) + val multiLineFixMessageRegex = Regex("8=FIX.*?10=[0-9]{3}(\\\\u0001)?(.)?", regexOptionsSet) + val fileText = file.text.substring(element.textOffset) + + val matches = multiLineFixMessageRegex.findAll(fileText) + for (match in matches) { + val fixMessage = match.value + val fixDelimiter = match.groupValues[1].ifEmpty { match.groupValues[2] } + if (match.range.contains(offset)) { + val parsedFixMessage = ParsedFixMessage(fixMessage, fixDelimiter, fixDataDictionaryService!!) + val fixMessageDocumentationTarget = FixMessageDocumentationTarget(parsedFixMessage) + fixMessages.add(fixMessageDocumentationTarget) + } } return fixMessages } -} \ No newline at end of file +} diff --git a/src/main/resources/META-INF/plugin.xml b/src/main/resources/META-INF/plugin.xml index f84be08..34eedbc 100644 --- a/src/main/resources/META-INF/plugin.xml +++ b/src/main/resources/META-INF/plugin.xml @@ -4,7 +4,7 @@ Quickfix XML Spec Dan Timofte - Adds inlay with the tag number to fields and groups in quickfix XML files. + Help for Quickfix XML files. FIX protocol message preview, inlay hints, documentation, and more. com.intellij.modules.platform diff --git a/src/main/resources/spec/FIXTest.xml b/src/main/resources/spec/FIXTest.xml new file mode 100644 index 0000000..285f510 --- /dev/null +++ b/src/main/resources/spec/FIXTest.xml @@ -0,0 +1,2953 @@ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
\ No newline at end of file