Untitled
UNIGINE guids.db parserunknown
python
9 months ago
6.3 kB
20
Indexable
# show_guid_tooltip_with_db.py
# Notepad++ PythonScript:
# - On hover, if pointer is over text like: guid://<hex>
# - Finds the closest "guids.db" near the currently opened file (same folder or any parent)
# - Parses it as a JSON mapping: "<hash>": "<relative/or/absolute/path>"
# - Shows the mapped filename in a Scintilla calltip; if missing, shows "GUID not found"
# Requirements: Install the free “PythonScript” plugin from Notepad++’s Plugins Admin,
# then add this as a new script and run it at startup.
import os
import re
import json
import io
from Npp import editor, notepad, SCINTILLANOTIFICATION
# ---------- Settings ----------
DWELL_MS = 500
TOKEN_RE = re.compile(r'guid://([0-9a-fA-F]+)') # captures the GUID hex after guid://
DB_FILENAME = "guids.db"
MAX_UPWARD_STEPS = 12 # how far up the folder tree to search
# -----------------------------
# Simple cache for DB lookups to avoid re-reading on every hover
_db_cache = {
# key: db_path -> {"mtime": float, "map": dict}
}
# Cache nearest DB per base folder (speed up path discovery)
_nearest_db_cache = {
# key: folder_path -> db_path or None
}
def _read_text(path):
# Try UTF-8 first, then fallback to system default
try:
with io.open(path, "r", encoding="utf-8") as f:
return f.read()
except UnicodeDecodeError:
with io.open(path, "r", encoding="mbcs", errors="replace") as f:
return f.read()
def _load_db(db_path):
"""Load/refresh the JSON mapping from db_path (with caching and light tolerance)."""
try:
mtime = os.path.getmtime(db_path)
except Exception:
return {}
cached = _db_cache.get(db_path)
if cached and cached.get("mtime") == mtime:
return cached.get("map", {})
raw = _read_text(db_path).strip()
mapping = {}
# Try strict JSON first
try:
mapping = json.loads(raw)
if not isinstance(mapping, dict):
mapping = {}
except Exception:
# Be tolerant with files that look like:
# "hash":"path",
# Wrap with braces if it's not already
text = raw
if not raw.lstrip().startswith("{"):
text = "{\n" + raw + "\n}"
# Remove trailing commas before closing brace to be nice
text = re.sub(r",\s*}", "}", text)
try:
mapping = json.loads(text)
if not isinstance(mapping, dict):
mapping = {}
except Exception:
mapping = {}
_db_cache[db_path] = {"mtime": mtime, "map": mapping}
return mapping
def _find_nearest_db(start_folder):
"""Find the closest guids.db: check folder, then parents up to MAX_UPWARD_STEPS."""
if not start_folder:
return None
# Use cached result if available
cached = _nearest_db_cache.get(start_folder)
if cached is not None:
return cached
folder = start_folder
steps = 0
while folder and steps <= MAX_UPWARD_STEPS:
candidate = os.path.join(folder, DB_FILENAME)
if os.path.isfile(candidate):
_nearest_db_cache[start_folder] = candidate
return candidate
# Move up one level
parent = os.path.dirname(folder)
if parent == folder:
break
folder = parent
steps += 1
_nearest_db_cache[start_folder] = None
return None
def _match_token_at_pos(pos):
"""Return (anchor_abs_pos, guid_hex) if hover is inside a guid:// token on that line."""
line_idx = editor.lineFromPosition(pos)
line_start = editor.positionFromLine(line_idx)
line_end = editor.getLineEndPosition(line_idx)
if line_end <= line_start:
return None
line_text = editor.getTextRange(line_start, line_end)
col = pos - line_start
for m in TOKEN_RE.finditer(line_text):
s, e = m.span()
if s <= col < e:
guid_hex = m.group(1).lower()
return (line_start + s, guid_hex)
return None
def _lookup_guid_name(guid_hex, base_folder):
"""Find nearest guids.db, load it, and return mapped filename or None."""
db_path = _find_nearest_db(base_folder)
if not db_path:
return None
db_map = _load_db(db_path)
# keys are expected without "guid://" prefix; we captured only hex
val = db_map.get(guid_hex)
# Some teams store uppercase keys; try a few variants:
if val is None:
val = db_map.get(guid_hex.upper())
return val
def _current_file_folder():
try:
fn = notepad.getCurrentFilename()
if fn:
return os.path.dirname(fn)
except Exception:
pass
return None
def on_dwell_start(args):
pos = args['position']
hit = _match_token_at_pos(pos)
if not hit:
return
anchor_abs, guid_hex = hit
base_folder = _current_file_folder()
mapped = _lookup_guid_name(guid_hex, base_folder)
if mapped:
tip = mapped
else:
# Friendly fallback message
tip = "GUID not found in nearest '{}'".format(DB_FILENAME)
editor.callTipShow(anchor_abs, tip)
def on_dwell_end(args):
editor.callTipCancel()
def _reset_caches_if_file_changed():
# When buffer changes, the "nearest db" may differ
_nearest_db_cache.clear()
# Keep _db_cache (so we reuse already loaded DBs), but you could also clear:
# _db_cache.clear()
def on_buffer_activated(args):
_reset_caches_if_file_changed()
def on_dir_changed(args):
# If user changed working dir, nearest-db resolution might change
_nearest_db_cache.clear()
# ----- Hook up everything -----
try:
editor.setMouseDwellTime(DWELL_MS)
editor.callback(on_dwell_start, [SCINTILLANOTIFICATION.DWELLSTART])
editor.callback(on_dwell_end, [SCINTILLANOTIFICATION.DWELLEND])
notepad.callback(on_buffer_activated, [NOTIFICATION.BUFFERACTIVATED])
notepad.callback(on_dir_changed, [NOTIFICATION.DIRTYFILESCHANGED]) # benign; can be removed
except Exception:
# Older PythonScript builds may not have every notification constant;
# the core hover logic will still work.
passEditor is loading...
Leave a Comment