Add syntax highlighting and formatting for request/response bodies

- Add automatic JSON and XML formatting for response bodies
- Replace TextView with GtkSourceView for syntax highlighting
- Add syntax selector dropdown (RAW/JSON/XML) for request body
- Use Source Code Pro font (12pt) for better readability
- Integrate with GNOME theme (Adwaita light/dark)
- Add line numbers and current line highlighting
- Persist syntax selection when saving/loading requests
- Maintain backward compatibility with existing saved requests
This commit is contained in:
vesp 2025-12-20 18:17:22 +01:00
parent 4499416a40
commit c9a01bf31e
2 changed files with 122 additions and 15 deletions

View File

@ -28,6 +28,7 @@ class HttpRequest:
url: str url: str
headers: Dict[str, str] # Key-value pairs headers: Dict[str, str] # Key-value pairs
body: str # Raw text body body: str # Raw text body
syntax: str = "RAW" # Syntax highlighting: "RAW", "JSON", or "XML"
def to_dict(self): def to_dict(self):
"""Convert to dictionary for JSON serialization.""" """Convert to dictionary for JSON serialization."""
@ -36,6 +37,9 @@ class HttpRequest:
@classmethod @classmethod
def from_dict(cls, data): def from_dict(cls, data):
"""Create instance from dictionary.""" """Create instance from dictionary."""
# Provide default for syntax field for backward compatibility
if 'syntax' not in data:
data = {**data, 'syntax': 'RAW'}
return cls(**data) return cls(**data)

View File

@ -126,11 +126,41 @@ class RosterWindow(Adw.ApplicationWindow):
"""Handle system theme changes.""" """Handle system theme changes."""
is_dark = style_manager.get_dark() is_dark = style_manager.get_dark()
style_scheme_manager = GtkSource.StyleSchemeManager.get_default() style_scheme_manager = GtkSource.StyleSchemeManager.get_default()
scheme_name = 'classic-dark' if is_dark else 'classic' scheme_name = 'Adwaita-dark' if is_dark else 'Adwaita'
scheme = style_scheme_manager.get_scheme(scheme_name) scheme = style_scheme_manager.get_scheme(scheme_name)
if scheme: if scheme:
self.response_body_sourceview.get_buffer().set_style_scheme(scheme) self.response_body_sourceview.get_buffer().set_style_scheme(scheme)
# Also update request body theme
self.body_sourceview.get_buffer().set_style_scheme(scheme)
def _setup_request_body_theme(self):
"""Set up GtkSourceView theme for request body."""
style_manager = Adw.StyleManager.get_default()
is_dark = style_manager.get_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.body_sourceview.get_buffer().set_style_scheme(scheme)
def _on_request_body_language_changed(self, dropdown, param):
"""Handle request body language selection change."""
selected = dropdown.get_selected()
language_manager = GtkSource.LanguageManager.get_default()
buffer = self.body_sourceview.get_buffer()
if selected == 0: # RAW
buffer.set_language(None)
elif selected == 1: # JSON
language = language_manager.get_language('json')
buffer.set_language(language)
elif selected == 2: # XML
language = language_manager.get_language('xml')
buffer.set_language(language)
def _create_actions(self): def _create_actions(self):
"""Create window-level actions.""" """Create window-level actions."""
@ -181,18 +211,70 @@ class RosterWindow(Adw.ApplicationWindow):
self.request_stack.add_titled(headers_box, "headers", "Headers") self.request_stack.add_titled(headers_box, "headers", "Headers")
# Body tab # Body tab with syntax highlighting
body_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=0)
# SourceView for request body
body_scroll = Gtk.ScrolledWindow() body_scroll = Gtk.ScrolledWindow()
body_scroll.set_vexpand(True) body_scroll.set_vexpand(True)
self.body_textview = Gtk.TextView() self.body_sourceview = GtkSource.View()
self.body_textview.set_monospace(True) self.body_sourceview.set_editable(True)
self.body_textview.set_left_margin(12) self.body_sourceview.set_show_line_numbers(True)
self.body_textview.set_right_margin(12) self.body_sourceview.set_highlight_current_line(True)
self.body_textview.set_top_margin(12) self.body_sourceview.set_left_margin(12)
self.body_textview.set_bottom_margin(12) self.body_sourceview.set_right_margin(12)
body_scroll.set_child(self.body_textview) self.body_sourceview.set_top_margin(12)
self.body_sourceview.set_bottom_margin(12)
self.request_stack.add_titled(body_scroll, "body", "Body") # Apply same font styling as response body
css_provider = Gtk.CssProvider()
css_provider.load_from_data(b"""
textview {
font-family: "Source Code Pro";
font-size: 12pt;
line-height: 1.2;
}
""")
self.body_sourceview.get_style_context().add_provider(
css_provider,
Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION
)
# Set up theme for request body
self._setup_request_body_theme()
body_scroll.set_child(self.body_sourceview)
body_box.append(body_scroll)
# Bottom toolbar with language selector
toolbar = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=6)
toolbar.set_margin_start(12)
toolbar.set_margin_end(12)
toolbar.set_margin_top(6)
toolbar.set_margin_bottom(6)
# Spacer to push dropdown to the right
spacer = Gtk.Box()
spacer.set_hexpand(True)
toolbar.append(spacer)
# Language selector label and dropdown
lang_label = Gtk.Label(label="Syntax:")
toolbar.append(lang_label)
lang_list = Gtk.StringList()
lang_list.append("RAW")
lang_list.append("JSON")
lang_list.append("XML")
self.body_language_dropdown = Gtk.DropDown(model=lang_list)
self.body_language_dropdown.set_selected(0) # Default to RAW
self.body_language_dropdown.connect("notify::selected", self._on_request_body_language_changed)
toolbar.append(self.body_language_dropdown)
body_box.append(toolbar)
self.request_stack.add_titled(body_box, "body", "Body")
def _setup_response_tabs(self): def _setup_response_tabs(self):
"""Create response tabs programmatically.""" """Create response tabs programmatically."""
@ -247,7 +329,7 @@ class RosterWindow(Adw.ApplicationWindow):
textview { textview {
font-family: "Source Code Pro"; font-family: "Source Code Pro";
font-size: 12pt; font-size: 12pt;
line-height: 1,2; line-height: 1.2;
} }
""") """)
self.response_body_sourceview.get_style_context().add_provider( self.response_body_sourceview.get_style_context().add_provider(
@ -334,10 +416,15 @@ class RosterWindow(Adw.ApplicationWindow):
child = child.get_next_sibling() child = child.get_next_sibling()
# Get body # Get body
buffer = self.body_textview.get_buffer() buffer = self.body_sourceview.get_buffer()
body = buffer.get_text(buffer.get_start_iter(), buffer.get_end_iter(), False) body = buffer.get_text(buffer.get_start_iter(), buffer.get_end_iter(), False)
return HttpRequest(method=method, url=url, headers=headers, body=body) # Get syntax selection
syntax_index = self.body_language_dropdown.get_selected()
syntax_options = ["RAW", "JSON", "XML"]
syntax = syntax_options[syntax_index]
return HttpRequest(method=method, url=url, headers=headers, body=body, syntax=syntax)
def _display_response(self, response): def _display_response(self, response):
"""Display response in UI.""" """Display response in UI."""
@ -522,9 +609,17 @@ class RosterWindow(Adw.ApplicationWindow):
self._add_header_row() self._add_header_row()
# Set body # Set body
buffer = self.body_textview.get_buffer() buffer = self.body_sourceview.get_buffer()
buffer.set_text(request.body) buffer.set_text(request.body)
# Restore syntax selection
syntax_options = ["RAW", "JSON", "XML"]
syntax = getattr(request, 'syntax', 'RAW') # Default to RAW for old requests
if syntax in syntax_options:
self.body_language_dropdown.set_selected(syntax_options.index(syntax))
else:
self.body_language_dropdown.set_selected(0) # Default to RAW
# Switch to headers tab # Switch to headers tab
self.request_stack.set_visible_child_name("headers") self.request_stack.set_visible_child_name("headers")
@ -767,9 +862,17 @@ class RosterWindow(Adw.ApplicationWindow):
self._add_header_row() self._add_header_row()
# Set body # Set body
buffer = self.body_textview.get_buffer() buffer = self.body_sourceview.get_buffer()
buffer.set_text(req.body) buffer.set_text(req.body)
# Restore syntax selection
syntax_options = ["RAW", "JSON", "XML"]
syntax = getattr(req, 'syntax', 'RAW') # Default to RAW for old requests
if syntax in syntax_options:
self.body_language_dropdown.set_selected(syntax_options.index(syntax))
else:
self.body_language_dropdown.set_selected(0) # Default to RAW
# Switch to headers tab # Switch to headers tab
self.request_stack.set_visible_child_name("headers") self.request_stack.set_visible_child_name("headers")