Add response body color highlighting
This commit is contained in:
parent
fed5a828f1
commit
9806333aa6
156
src/window.py
156
src/window.py
@ -17,7 +17,9 @@
|
|||||||
#
|
#
|
||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
from gi.repository import Adw, Gtk, GLib, Gio
|
import gi
|
||||||
|
gi.require_version('GtkSource', '5')
|
||||||
|
from gi.repository import Adw, Gtk, GLib, Gio, GtkSource
|
||||||
from .models import HttpRequest, HttpResponse, HistoryEntry
|
from .models import HttpRequest, HttpResponse, HistoryEntry
|
||||||
from .http_client import HttpClient
|
from .http_client import HttpClient
|
||||||
from .history_manager import HistoryManager
|
from .history_manager import HistoryManager
|
||||||
@ -28,6 +30,8 @@ from .widgets.history_item import HistoryItem
|
|||||||
from .widgets.project_item import ProjectItem
|
from .widgets.project_item import ProjectItem
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
import threading
|
import threading
|
||||||
|
import json
|
||||||
|
import xml.dom.minidom
|
||||||
|
|
||||||
|
|
||||||
@Gtk.Template(resource_path='/cz/vesp/roster/main-window.ui')
|
@Gtk.Template(resource_path='/cz/vesp/roster/main-window.ui')
|
||||||
@ -95,6 +99,39 @@ class RosterWindow(Adw.ApplicationWindow):
|
|||||||
self.split_pane.set_position(allocation.width // 2)
|
self.split_pane.set_position(allocation.width // 2)
|
||||||
return False # Don't repeat
|
return False # Don't repeat
|
||||||
|
|
||||||
|
def _setup_sourceview_theme(self):
|
||||||
|
"""Set up GtkSourceView theme based on system color scheme."""
|
||||||
|
# Get the style manager to detect dark mode
|
||||||
|
style_manager = Adw.StyleManager.get_default()
|
||||||
|
is_dark = style_manager.get_dark()
|
||||||
|
|
||||||
|
# Get the appropriate color scheme (using classic for more subtle colors)
|
||||||
|
# You can change these to customize the color theme:
|
||||||
|
# - 'classic' / 'classic-dark' (subtle, muted colors)
|
||||||
|
# - 'Adwaita' / 'Adwaita-dark' (GNOME default)
|
||||||
|
# - 'tango' (colorful)
|
||||||
|
# - 'solarized-light' / 'solarized-dark' (popular alternative)
|
||||||
|
# - 'kate' / 'kate-dark'
|
||||||
|
style_scheme_manager = GtkSource.StyleSchemeManager.get_default()
|
||||||
|
scheme_name = 'Adwaita-dark' if is_dark else 'Adwaita'
|
||||||
|
scheme = style_scheme_manager.get_scheme(scheme_name)
|
||||||
|
|
||||||
|
if scheme:
|
||||||
|
self.response_body_sourceview.get_buffer().set_style_scheme(scheme)
|
||||||
|
|
||||||
|
# Listen for theme changes
|
||||||
|
style_manager.connect('notify::dark', self._on_theme_changed)
|
||||||
|
|
||||||
|
def _on_theme_changed(self, style_manager, param):
|
||||||
|
"""Handle system theme changes."""
|
||||||
|
is_dark = style_manager.get_dark()
|
||||||
|
style_scheme_manager = GtkSource.StyleSchemeManager.get_default()
|
||||||
|
scheme_name = 'classic-dark' if is_dark else 'classic'
|
||||||
|
scheme = style_scheme_manager.get_scheme(scheme_name)
|
||||||
|
|
||||||
|
if scheme:
|
||||||
|
self.response_body_sourceview.get_buffer().set_style_scheme(scheme)
|
||||||
|
|
||||||
def _create_actions(self):
|
def _create_actions(self):
|
||||||
"""Create window-level actions."""
|
"""Create window-level actions."""
|
||||||
pass
|
pass
|
||||||
@ -186,17 +223,39 @@ class RosterWindow(Adw.ApplicationWindow):
|
|||||||
|
|
||||||
self.response_stack.add_titled(headers_scroll, "headers", "Headers")
|
self.response_stack.add_titled(headers_scroll, "headers", "Headers")
|
||||||
|
|
||||||
# Response Body tab
|
# Response Body tab with syntax highlighting
|
||||||
body_scroll = Gtk.ScrolledWindow()
|
body_scroll = Gtk.ScrolledWindow()
|
||||||
body_scroll.set_vexpand(True)
|
body_scroll.set_vexpand(True)
|
||||||
self.response_body_textview = Gtk.TextView()
|
self.response_body_sourceview = GtkSource.View()
|
||||||
self.response_body_textview.set_editable(False)
|
self.response_body_sourceview.set_editable(False)
|
||||||
self.response_body_textview.set_monospace(True)
|
self.response_body_sourceview.set_show_line_numbers(True)
|
||||||
self.response_body_textview.set_left_margin(12)
|
self.response_body_sourceview.set_highlight_current_line(True)
|
||||||
self.response_body_textview.set_right_margin(12)
|
self.response_body_sourceview.set_left_margin(12)
|
||||||
self.response_body_textview.set_top_margin(12)
|
self.response_body_sourceview.set_right_margin(12)
|
||||||
self.response_body_textview.set_bottom_margin(12)
|
self.response_body_sourceview.set_top_margin(12)
|
||||||
body_scroll.set_child(self.response_body_textview)
|
self.response_body_sourceview.set_bottom_margin(12)
|
||||||
|
|
||||||
|
# Set up syntax highlighting with system theme colors
|
||||||
|
self._setup_sourceview_theme()
|
||||||
|
|
||||||
|
# Set font family and size
|
||||||
|
# You can customize these values:
|
||||||
|
# - font-family: Change to any font (e.g., "Monospace", "JetBrains Mono", "Fira Code")
|
||||||
|
# - font-size: Change to any size (e.g., 10pt, 12pt, 14pt, 16pt)
|
||||||
|
css_provider = Gtk.CssProvider()
|
||||||
|
css_provider.load_from_data(b"""
|
||||||
|
textview {
|
||||||
|
font-family: "Source Code Pro";
|
||||||
|
font-size: 12pt;
|
||||||
|
line-height: 1,2;
|
||||||
|
}
|
||||||
|
""")
|
||||||
|
self.response_body_sourceview.get_style_context().add_provider(
|
||||||
|
css_provider,
|
||||||
|
Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION
|
||||||
|
)
|
||||||
|
|
||||||
|
body_scroll.set_child(self.response_body_sourceview)
|
||||||
|
|
||||||
self.response_stack.add_titled(body_scroll, "body", "Body")
|
self.response_stack.add_titled(body_scroll, "body", "Body")
|
||||||
|
|
||||||
@ -294,9 +353,17 @@ class RosterWindow(Adw.ApplicationWindow):
|
|||||||
buffer = self.response_headers_textview.get_buffer()
|
buffer = self.response_headers_textview.get_buffer()
|
||||||
buffer.set_text(response.headers)
|
buffer.set_text(response.headers)
|
||||||
|
|
||||||
# Update response body
|
# Extract content-type and format body
|
||||||
buffer = self.response_body_textview.get_buffer()
|
content_type = self._extract_content_type(response.headers)
|
||||||
buffer.set_text(response.body)
|
formatted_body = self._format_response_body(response.body, content_type)
|
||||||
|
|
||||||
|
# Update response body with syntax highlighting
|
||||||
|
source_buffer = self.response_body_sourceview.get_buffer()
|
||||||
|
source_buffer.set_text(formatted_body)
|
||||||
|
|
||||||
|
# Set language for syntax highlighting
|
||||||
|
language = self._get_language_from_content_type(content_type)
|
||||||
|
source_buffer.set_language(language)
|
||||||
|
|
||||||
def _display_error(self, error):
|
def _display_error(self, error):
|
||||||
"""Display error in UI."""
|
"""Display error in UI."""
|
||||||
@ -308,12 +375,71 @@ class RosterWindow(Adw.ApplicationWindow):
|
|||||||
buffer.set_text("")
|
buffer.set_text("")
|
||||||
|
|
||||||
# Display error in body
|
# Display error in body
|
||||||
buffer = self.response_body_textview.get_buffer()
|
source_buffer = self.response_body_sourceview.get_buffer()
|
||||||
buffer.set_text(error)
|
source_buffer.set_text(error)
|
||||||
|
source_buffer.set_language(None) # No syntax highlighting for errors
|
||||||
|
|
||||||
# Show toast
|
# Show toast
|
||||||
self._show_toast(f"Request failed: {error}")
|
self._show_toast(f"Request failed: {error}")
|
||||||
|
|
||||||
|
def _extract_content_type(self, headers_text):
|
||||||
|
"""Extract content-type from response headers."""
|
||||||
|
if not headers_text:
|
||||||
|
return ""
|
||||||
|
|
||||||
|
# Parse headers line by line
|
||||||
|
for line in headers_text.split('\n'):
|
||||||
|
line = line.strip()
|
||||||
|
if line.lower().startswith('content-type:'):
|
||||||
|
# Extract value after colon
|
||||||
|
return line.split(':', 1)[1].strip().lower()
|
||||||
|
|
||||||
|
return ""
|
||||||
|
|
||||||
|
def _get_language_from_content_type(self, content_type):
|
||||||
|
"""Get GtkSourceView language ID from content type."""
|
||||||
|
if not content_type:
|
||||||
|
return None
|
||||||
|
|
||||||
|
language_manager = GtkSource.LanguageManager.get_default()
|
||||||
|
|
||||||
|
# Map content types to language IDs
|
||||||
|
if 'application/json' in content_type or 'text/json' in content_type:
|
||||||
|
return language_manager.get_language('json')
|
||||||
|
elif 'application/xml' in content_type or 'text/xml' in content_type:
|
||||||
|
return language_manager.get_language('xml')
|
||||||
|
elif 'text/html' in content_type:
|
||||||
|
return language_manager.get_language('html')
|
||||||
|
elif 'application/javascript' in content_type or 'text/javascript' in content_type:
|
||||||
|
return language_manager.get_language('js')
|
||||||
|
elif 'text/css' in content_type:
|
||||||
|
return language_manager.get_language('css')
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
def _format_response_body(self, body, content_type):
|
||||||
|
"""Format response body based on content type."""
|
||||||
|
if not body or not body.strip():
|
||||||
|
return body
|
||||||
|
|
||||||
|
try:
|
||||||
|
# JSON formatting
|
||||||
|
if 'application/json' in content_type or 'text/json' in content_type:
|
||||||
|
parsed = json.loads(body)
|
||||||
|
return json.dumps(parsed, indent=2, ensure_ascii=False)
|
||||||
|
|
||||||
|
# XML formatting
|
||||||
|
elif 'application/xml' in content_type or 'text/xml' in content_type:
|
||||||
|
dom = xml.dom.minidom.parseString(body)
|
||||||
|
return dom.toprettyxml(indent=" ")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
# If formatting fails, return original body
|
||||||
|
print(f"Failed to format body: {e}")
|
||||||
|
|
||||||
|
# Return original for other content types or if formatting failed
|
||||||
|
return body
|
||||||
|
|
||||||
def on_add_header_clicked(self, button):
|
def on_add_header_clicked(self, button):
|
||||||
"""Add new header row."""
|
"""Add new header row."""
|
||||||
self._add_header_row()
|
self._add_header_row()
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user