Highlight matched line when matching on specific var values

Very helpful for debugging ObjectUpdates which are high frequency
and have many diff objects in a single message.

Just the first line of the var for now. Need to be smarter about
how we build the blocks in the message text if we want to highlight
the whole thing.
This commit is contained in:
Salad Dais
2021-05-05 04:14:03 +00:00
parent 8989843042
commit 02c212e4a6
5 changed files with 52 additions and 16 deletions

View File

@@ -301,8 +301,6 @@ If you are a viewer developer, please put them in a viewer.
* AISv3 wrapper?
* Higher level wrappers for common things? I don't really need these, so only if people want to write them.
* Highlight matched portion of message in log view, if applicable
* * Remember deep filters and return a map of them, have message formatter return text ranges?
* Move things out of `templates.py`, right now most binary serialization stuff lives there
because it's more convenient for me to hot-reload.
* Ability to add menus?

View File

@@ -35,7 +35,7 @@ from hippolyzer.lib.proxy.ca_utils import setup_ca_everywhere
from hippolyzer.lib.proxy.caps_client import CapsClient
from hippolyzer.lib.proxy.http_proxy import create_proxy_master, HTTPFlowContext
from hippolyzer.lib.proxy.packets import Direction
from hippolyzer.lib.proxy.message import ProxiedMessage, VerbatimHumanVal, proxy_eval
from hippolyzer.lib.proxy.message import ProxiedMessage, VerbatimHumanVal, proxy_eval, SpannedString
from hippolyzer.lib.proxy.message_logger import LLUDPMessageLogEntry, AbstractMessageLogEntry
from hippolyzer.lib.proxy.region import ProxiedRegion
from hippolyzer.lib.proxy.sessions import Session, SessionManager
@@ -161,6 +161,8 @@ class ProxyGUI(QtWidgets.QMainWindow):
"ViewerAsset GetTexture SetAlwaysRun GetDisplayNames MapImageService MapItemReply".split(" ")
DEFAULT_FILTER = f"!({' || '.join(ignored for ignored in DEFAULT_IGNORE)})"
textRequest: QtWidgets.QTextEdit
def __init__(self):
super().__init__()
loadUi(MAIN_WINDOW_UI_PATH, self)
@@ -263,6 +265,12 @@ class ProxyGUI(QtWidgets.QMainWindow):
beautify=self.checkBeautify.isChecked(),
replacements=self.buildReplacements(entry.session, entry.region),
)
highlight_range = None
if isinstance(req, SpannedString):
match_result = self.model.filter.match(entry)
# Match result was a tuple indicating what matched
if isinstance(match_result, tuple):
highlight_range = req.spans.get(match_result)
resp = entry.response(beautify=self.checkBeautify.isChecked())
self.textRequest.setPlainText(req)
if resp:
@@ -271,6 +279,13 @@ class ProxyGUI(QtWidgets.QMainWindow):
else:
self.textResponse.hide()
if highlight_range:
cursor = self.textRequest.textCursor()
cursor.setPosition(highlight_range[0], QtGui.QTextCursor.KeepAnchor)
highlight_format = QtGui.QTextBlockFormat()
highlight_format.setBackground(QtCore.Qt.yellow)
cursor.setBlockFormat(highlight_format)
def beforeInsert(self):
vbar = self.tableView.verticalScrollBar()
self._shouldScrollOnInsert = vbar.value() == vbar.maximum()

View File

@@ -5,6 +5,7 @@ import logging
import math
import os
import re
import typing
import uuid
from typing import *
@@ -71,6 +72,14 @@ def proxy_eval(eval_str: str, globals_=None, locals_=None):
)
TextSpan = Tuple[int, int]
SpanDict = Dict[Tuple[Union[str, int], ...], TextSpan]
class SpannedString(str):
spans: SpanDict = {}
class ProxiedMessage(Message):
__slots__ = ("meta", "injected", "dropped", "direction")
@@ -83,9 +92,10 @@ class ProxiedMessage(Message):
_maybe_reload_templates()
def to_human_string(self, replacements=None, beautify=False,
template: Optional[MessageTemplate] = None):
template: Optional[MessageTemplate] = None) -> SpannedString:
replacements = replacements or {}
_maybe_reload_templates()
spans: SpanDict = {}
string = ""
if self.direction is not None:
string += f'{self.direction.name} '
@@ -101,11 +111,17 @@ class ProxiedMessage(Message):
block_suffix = ""
if template and template.get_block(block_name).block_type == MsgBlockType.MBT_VARIABLE:
block_suffix = ' # Variable'
for block in block_list:
for block_num, block in enumerate(block_list):
string += f"[{block_name}]{block_suffix}\n"
for var_name, val in block.items():
start_len = len(string)
string += self._format_var(block, var_name, val, replacements, beautify)
return string
end_len = len(string)
# Store the spans for each var so we can highlight specific matches
spans[(self.name, block_name, block_num, var_name)] = (start_len, end_len)
spanned = SpannedString(string)
spanned.spans = spans
return spanned
def _format_var(self, block, var_name, var_val, replacements, beautify=False):
string = ""

View File

@@ -62,9 +62,12 @@ def message_filter():
return expression, EOF
MATCH_RESULT = typing.Union[bool, typing.Tuple]
class BaseFilterNode(abc.ABC):
@abc.abstractmethod
def match(self, msg) -> bool:
def match(self, msg) -> MATCH_RESULT:
raise NotImplementedError()
@property
@@ -94,17 +97,17 @@ class BinaryFilterNode(BaseFilterNode, abc.ABC):
class UnaryNotFilterNode(UnaryFilterNode):
def match(self, msg) -> bool:
def match(self, msg) -> MATCH_RESULT:
return not self.node.match(msg)
class OrFilterNode(BinaryFilterNode):
def match(self, msg) -> bool:
def match(self, msg) -> MATCH_RESULT:
return self.left_node.match(msg) or self.right_node.match(msg)
class AndFilterNode(BinaryFilterNode):
def match(self, msg) -> bool:
def match(self, msg) -> MATCH_RESULT:
return self.left_node.match(msg) and self.right_node.match(msg)
@@ -114,7 +117,7 @@ class MessageFilterNode(BaseFilterNode):
self.operator = operator
self.value = value
def match(self, msg) -> bool:
def match(self, msg) -> MATCH_RESULT:
return msg.matches(self)
@property

View File

@@ -586,15 +586,19 @@ class LLUDPMessageLogEntry(AbstractMessageLogEntry):
for block_name in message.blocks:
if not fnmatch.fnmatchcase(block_name, matcher.selector[1]):
continue
for block in message[block_name]:
for block_num, block in enumerate(message[block_name]):
for var_name in block.vars.keys():
if not fnmatch.fnmatchcase(var_name, matcher.selector[2]):
continue
# So we know where the match happened
span_key = (message.name, block_name, block_num, var_name)
if selector_len == 3:
# We're just matching on the var existing, not having any particular value
if matcher.value is None:
return True
return span_key
if self._val_matches(matcher.operator, block[var_name], matcher.value):
return True
return span_key
# Need to invoke a special unpacker
elif selector_len == 4:
try:
deserialized = block.deserialize_var(var_name)
@@ -608,9 +612,9 @@ class LLUDPMessageLogEntry(AbstractMessageLogEntry):
for key in deserialized.keys():
if fnmatch.fnmatchcase(str(key), matcher.selector[3]):
if matcher.value is None:
return True
return span_key
if self._val_matches(matcher.operator, deserialized[key], matcher.value):
return True
return span_key
return False