├── screenshot.png ├── test-stuff ├── broken-should-work.http ├── new-test.http └── test.http ├── src └── main │ ├── antlr4 │ ├── input.http │ ├── HTTPLexer.g4 │ └── HTTPParser.g4 │ ├── resources │ └── com │ │ └── javierllorente │ │ └── netbeans │ │ └── rest │ │ └── client │ │ ├── restservice.png │ │ ├── http │ │ └── editor │ │ │ └── http.png │ │ ├── Bundle.properties │ │ ├── prettyprint.xsl │ │ ├── FontAndColors.xml │ │ ├── HTTPExample.http │ │ └── layer.xml │ ├── java │ └── com │ │ └── javierllorente │ │ └── netbeans │ │ └── rest │ │ ├── client │ │ ├── package-info.java │ │ ├── editor │ │ │ ├── RestMediaType.java │ │ │ ├── XmlEditorKit.java │ │ │ └── JsonEditorKit.java │ │ ├── http │ │ │ └── editor │ │ │ │ ├── syntax │ │ │ │ ├── antlr │ │ │ │ │ ├── HTTPLexer.tokens │ │ │ │ │ └── HTTPParser.tokens │ │ │ │ ├── coloring │ │ │ │ │ ├── HTTPTokenId.java │ │ │ │ │ └── HTTPLanguage.java │ │ │ │ ├── HTTPLangLexer.java │ │ │ │ └── HTTPLangParser.java │ │ │ │ ├── sidebar │ │ │ │ ├── request │ │ │ │ │ ├── IRequestProcessor.java │ │ │ │ │ ├── Request.java │ │ │ │ │ └── RequestProcessor.java │ │ │ │ ├── RunHttpRequestsSideBarPanel.java │ │ │ │ ├── ResponseSideBarFactory.java │ │ │ │ ├── RunHttpRequestsSideBarFactory.java │ │ │ │ ├── ActionPopup.java │ │ │ │ ├── ResponseSidebarManager.java │ │ │ │ ├── DrawingPanel.java │ │ │ │ └── ResponseSidebarPanel.java │ │ │ │ └── navigator │ │ │ │ ├── HTTPLangStructureScanner.java │ │ │ │ └── HTTPLangStructureItem.java │ │ ├── hyperlink │ │ │ ├── RestURLDisplayer.java │ │ │ └── RestHyperlinkProvider.java │ │ ├── ui │ │ │ ├── StatusLabel.java │ │ │ ├── actions │ │ │ │ ├── OpenInUIAction.java │ │ │ │ └── OpenInEditorAction.java │ │ │ ├── RestClientOptionsOptionsPanelController.java │ │ │ ├── UrlPanel.form │ │ │ ├── LineNumberComponent.java │ │ │ ├── RestClientOptionsPanel.form │ │ │ ├── UrlPanel.java │ │ │ ├── BodyPanel.java │ │ │ ├── BodyPanel.form │ │ │ ├── TablePanel.form │ │ │ ├── RestClientTopComponent.form │ │ │ └── ResponsePanel.java │ │ ├── UserAgent.java │ │ ├── event │ │ │ ├── UrlDocumentListener.java │ │ │ ├── CellDocumentListener.java │ │ │ ├── TabChangeListener.java │ │ │ ├── TokenDocumentListener.java │ │ │ └── TableParamsListener.java │ │ ├── parsers │ │ │ ├── CellParamsParser.java │ │ │ ├── UrlParamsParser.java │ │ │ ├── PostmanUtilities.java │ │ │ └── Parser.java │ │ └── util │ │ │ └── HttpFileUtils.java │ │ └── util │ │ ├── ExceptionUtils.java │ │ └── FormatUtils.java │ └── nbm │ └── manifest.mf ├── NOTICE ├── .gitignore ├── README.md └── nb-configuration.xml /screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/javierllorente/netbeans-rest-client/HEAD/screenshot.png -------------------------------------------------------------------------------- /test-stuff/broken-should-work.http: -------------------------------------------------------------------------------- 1 | # Beispiel für eine benutzerdefinierte (extension) Methode 2 | FOO /custom HTTP/1.1 -------------------------------------------------------------------------------- /src/main/antlr4/input.http: -------------------------------------------------------------------------------- 1 | GET /index.html HTTP/1.1 2 | ### 3 | GET /folder/ HTTP/1.1 4 | a:d 5 | ### 6 | GET twitter.com 7 | d:d 8 | 9 | { 10 | "d":3 11 | } -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | REST Client (netbeans-rest-client) 2 | Copyright 2022-2023 Javier Llorente 3 | 4 | This software contains code derived from Apache NetBeans 5 | Copyright 2017-2022 The Apache Software Foundation 6 | -------------------------------------------------------------------------------- /src/main/resources/com/javierllorente/netbeans/rest/client/restservice.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/javierllorente/netbeans-rest-client/HEAD/src/main/resources/com/javierllorente/netbeans/rest/client/restservice.png -------------------------------------------------------------------------------- /src/main/resources/com/javierllorente/netbeans/rest/client/http/editor/http.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/javierllorente/netbeans-rest-client/HEAD/src/main/resources/com/javierllorente/netbeans/rest/client/http/editor/http.png -------------------------------------------------------------------------------- /test-stuff/new-test.http: -------------------------------------------------------------------------------- 1 | GET / 2 | Accept: application/json 3 | Authorization: tt 4 | 5 | {} 6 | 7 | 8 | ### 9 | / 10 | Accept: r 11 | 12 | ### 13 | GET / 14 | 15 | 16 | ### 17 | ### 18 | GET http://google.de 19 | Accept: */* 20 | Content-Type: application/json -------------------------------------------------------------------------------- /src/main/resources/com/javierllorente/netbeans/rest/client/Bundle.properties: -------------------------------------------------------------------------------- 1 | OpenIDE-Module-Name=REST Client 2 | 3 | Editors/text/x-http=HTTP File 4 | 5 | keyword=Keyword 6 | number=Number 7 | digit=Digit 8 | field=Field 9 | method=Method 10 | comment=Comment 11 | whitespace=Whitespace 12 | number=Number 13 | string=String 14 | request_separator=Request Separator 15 | whitespace=Whitespace 16 | mod-method=HTTP Protocol -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled class file 2 | *.class 3 | 4 | # Log file 5 | *.log 6 | 7 | # BlueJ files 8 | *.ctxt 9 | 10 | # Mobile Tools for Java (J2ME) 11 | .mtj.tmp/ 12 | 13 | # Package Files # 14 | *.jar 15 | *.war 16 | *.nar 17 | *.ear 18 | *.zip 19 | *.tar.gz 20 | *.rar 21 | 22 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 23 | hs_err_pid* 24 | /target/ 25 | /src/main/antlr4/.antlr/ 26 | -------------------------------------------------------------------------------- /src/main/java/com/javierllorente/netbeans/rest/client/package-info.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Click nbfs://nbhost/SystemFileSystem/Templates/Licenses/license-default.txt to change this license 3 | * Click nbfs://nbhost/SystemFileSystem/Templates/Classes/package-info.java to edit this template 4 | */ 5 | @TemplateRegistration(folder = "Other", content = "HTTPExample.http") 6 | package com.javierllorente.netbeans.rest.client; 7 | 8 | import org.netbeans.api.templates.TemplateRegistration; 9 | -------------------------------------------------------------------------------- /src/main/resources/com/javierllorente/netbeans/rest/client/prettyprint.xsl: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/main/nbm/manifest.mf: -------------------------------------------------------------------------------- 1 | Manifest-Version: 1.0 2 | OpenIDE-Module-Layer: com/javierllorente/netbeans/rest/client/layer.xml 3 | OpenIDE-Module-Localizing-Bundle: com/javierllorente/netbeans/rest/client/Bundle.properties 4 | OpenIDE-Module-Requires: org.openide.windows.WindowManager 5 | OpenIDE-Module-Recommends: cnb.org.netbeans.modules.javascript2.editor 6 | OpenIDE-Module-Name: REST Client 7 | OpenIDE-Module-Display-Category: Tools 8 | OpenIDE-Module-Short-Description: A REST client for NetBeans 9 | OpenIDE-Module-Long-Description: Provides a basic REST client for NetBeans 10 | OpenIDE-Module-Implementation-Version: 0.9.0 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # netbeans-rest-client 2 | A REST client for NetBeans 3 | 4 | Features 5 | - Sending GET, POST, PUT, PATCH or DELETE requests 6 | - Authorisation: Basic Auth and Bearer Token 7 | - Adding headers 8 | - Adding body (text, JSON or XML) 9 | - Displaying response headers 10 | - Making JSON, XML or HTML response pretty/ugly 11 | - Opening links of a JSON/XML response in a new tab 12 | - Importing from/exporting to Postman collections 13 | - .http file editor 14 | 15 | Instructions 16 | - Install the module (Tools -> Plugins -> Available Plugins) 17 | - Then go to Tools -> REST Client -> Enjoy? 18 | 19 | ![screenshot](screenshot.png) 20 | -------------------------------------------------------------------------------- /src/main/resources/com/javierllorente/netbeans/rest/client/FontAndColors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/main/resources/com/javierllorente/netbeans/rest/client/HTTPExample.http: -------------------------------------------------------------------------------- 1 | https://jsonplaceholder.typicode.com/todos/1 2 | 3 | ### Request 1 4 | 5 | // POST request with json payload 6 | POST https://google.de HTTP/1.1 7 | content-type: application/json 8 | 9 | # Test 10 | { 11 | "id":12, 12 | "time": "Wed, 21 Oct 2015 18:27:50 GMT", 13 | "path": "./test/test", 14 | "urlstring": "https://google.de", 15 | "commentstring": "//google.de", 16 | "seperatorstring": "###google.de", 17 | "methodstring": "GET", 18 | "header": "content-type: application/json", 19 | "array": ["1", 3, "foo", "bar", 2, "baz"], 20 | "": 33, 21 | "foo": { 22 | "foo": 12, 23 | "footer": { 24 | "test": 12, 25 | "bar": "asd", 26 | "baz": "asd" 27 | } 28 | } 29 | } 30 | 31 | ### Request 2 32 | 33 | GET http://test.de HTTP/1.2 34 | content-type: application/json 35 | 36 | ### Request 2 37 | 38 | GET http://google.de HTTP/1.1 39 | 40 | { 41 | "test": "13", 42 | "test": { 43 | "test": 12 44 | } 45 | } -------------------------------------------------------------------------------- /nb-configuration.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 9 | 10 | 16 | JDK_17 17 | 18 | 19 | -------------------------------------------------------------------------------- /src/main/java/com/javierllorente/netbeans/rest/client/editor/RestMediaType.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 Javier Llorente . 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.javierllorente.netbeans.rest.client.editor; 17 | 18 | /** 19 | * 20 | * @author Javier Llorente 21 | */ 22 | public class RestMediaType { 23 | public static final String XML = "text/nbrc-content+xml"; 24 | public static final String JSON = "text/nbrc-content+x-json"; 25 | 26 | private RestMediaType() { 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/com/javierllorente/netbeans/rest/client/http/editor/syntax/antlr/HTTPLexer.tokens: -------------------------------------------------------------------------------- 1 | OPEN_BLOCK_BRAKET=1 2 | CLOSE_BLOCK_BRAKET=2 3 | WS=3 4 | BODY_START_WITH_BLANK=4 5 | GENERIC_BODY_START=5 6 | NEWLINE=6 7 | REQUEST_SEPARATOR=7 8 | COMMENT=8 9 | METHOD=9 10 | HTTP_PROTOCOL=10 11 | COLON=11 12 | DOT=12 13 | COMMA=13 14 | SLASH=14 15 | QUESTION_MARK=15 16 | HASH=16 17 | ASTERISK=17 18 | EQUAL=18 19 | AMPERSAND=19 20 | PERCENT=20 21 | OPEN_BRAKET=21 22 | CLOSE_BRAKET=22 23 | QUOTE=23 24 | DIGITS=24 25 | SCHEME=25 26 | ALPHA_CHARS=26 27 | DASH=27 28 | UNDERSCORE=28 29 | SCHEME_SEPARATOR=29 30 | INPUT_FILE_REF=30 31 | RESPONSE_HANDLER_SCRIPT_START=31 32 | RESPONSE_HANDLER_SCRIPT_END=32 33 | RESPONSE_HANDLER_FILE_REF=33 34 | SCRIPT_CONTENT=34 35 | ENV_VARIABLE=35 36 | MULTIPART_BOUNDARY=36 37 | MULTIPART_PART=37 38 | BODY_START_NO_BLANK=38 39 | ERROR=39 40 | STRING=40 41 | NUMBER=41 42 | TRUE=42 43 | FALSE=43 44 | NULL=44 45 | BODY_TEXT=45 46 | JSON_RBRACE=46 47 | 'HTTP'=10 48 | '.'=12 49 | '/'=14 50 | '?'=15 51 | '#'=16 52 | '*'=17 53 | '='=18 54 | '&'=19 55 | '%'=20 56 | '"'=23 57 | '-'=27 58 | '_'=28 59 | '://'=29 60 | '%}'=32 61 | '}'=46 62 | 'true'=42 63 | 'false'=43 64 | 'null'=44 65 | -------------------------------------------------------------------------------- /src/main/java/com/javierllorente/netbeans/rest/client/http/editor/syntax/antlr/HTTPParser.tokens: -------------------------------------------------------------------------------- 1 | OPEN_BLOCK_BRAKET=1 2 | CLOSE_BLOCK_BRAKET=2 3 | WS=3 4 | BODY_START_WITH_BLANK=4 5 | GENERIC_BODY_START=5 6 | NEWLINE=6 7 | REQUEST_SEPARATOR=7 8 | COMMENT=8 9 | METHOD=9 10 | HTTP_PROTOCOL=10 11 | COLON=11 12 | DOT=12 13 | COMMA=13 14 | SLASH=14 15 | QUESTION_MARK=15 16 | HASH=16 17 | ASTERISK=17 18 | EQUAL=18 19 | AMPERSAND=19 20 | PERCENT=20 21 | OPEN_BRAKET=21 22 | CLOSE_BRAKET=22 23 | QUOTE=23 24 | DIGITS=24 25 | SCHEME=25 26 | ALPHA_CHARS=26 27 | DASH=27 28 | UNDERSCORE=28 29 | SCHEME_SEPARATOR=29 30 | INPUT_FILE_REF=30 31 | RESPONSE_HANDLER_SCRIPT_START=31 32 | RESPONSE_HANDLER_SCRIPT_END=32 33 | RESPONSE_HANDLER_FILE_REF=33 34 | SCRIPT_CONTENT=34 35 | ENV_VARIABLE=35 36 | MULTIPART_BOUNDARY=36 37 | MULTIPART_PART=37 38 | BODY_START_NO_BLANK=38 39 | ERROR=39 40 | STRING=40 41 | NUMBER=41 42 | TRUE=42 43 | FALSE=43 44 | NULL=44 45 | BODY_TEXT=45 46 | JSON_RBRACE=46 47 | 'HTTP'=10 48 | '.'=12 49 | '/'=14 50 | '?'=15 51 | '#'=16 52 | '*'=17 53 | '='=18 54 | '&'=19 55 | '%'=20 56 | '"'=23 57 | '-'=27 58 | '_'=28 59 | '://'=29 60 | '%}'=32 61 | '}'=46 62 | 'true'=42 63 | 'false'=43 64 | 'null'=44 65 | -------------------------------------------------------------------------------- /src/main/java/com/javierllorente/netbeans/rest/client/editor/XmlEditorKit.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 Javier Llorente . 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.javierllorente.netbeans.rest.client.editor; 17 | 18 | import javax.swing.text.EditorKit; 19 | import org.netbeans.api.editor.mimelookup.MimeRegistration; 20 | import org.netbeans.modules.editor.NbEditorKit; 21 | 22 | /** 23 | * 24 | * @author Javier Llorente 25 | */ 26 | @MimeRegistration(mimeType = RestMediaType.XML, service = EditorKit.class) 27 | public class XmlEditorKit extends NbEditorKit { 28 | 29 | @Override 30 | public String getContentType() { 31 | return RestMediaType.XML; 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/com/javierllorente/netbeans/rest/client/editor/JsonEditorKit.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 Javier Llorente . 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.javierllorente.netbeans.rest.client.editor; 17 | 18 | import javax.swing.text.EditorKit; 19 | import org.netbeans.api.editor.mimelookup.MimeRegistration; 20 | import org.netbeans.modules.editor.NbEditorKit; 21 | 22 | /** 23 | * 24 | * @author Javier Llorente 25 | */ 26 | @MimeRegistration(mimeType = RestMediaType.JSON, service = EditorKit.class) 27 | public class JsonEditorKit extends NbEditorKit { 28 | 29 | @Override 30 | public String getContentType() { 31 | return RestMediaType.JSON; 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/com/javierllorente/netbeans/rest/client/hyperlink/RestURLDisplayer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 Javier Llorente . 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.javierllorente.netbeans.rest.client.hyperlink; 17 | 18 | import com.javierllorente.netbeans.rest.client.ui.RestClientTopComponent; 19 | import java.net.URL; 20 | import org.openide.awt.HtmlBrowser; 21 | import org.openide.util.lookup.ServiceProvider; 22 | 23 | /** 24 | * 25 | * @author Javier Llorente 26 | */ 27 | @ServiceProvider(service = HtmlBrowser.URLDisplayer.class) 28 | public class RestURLDisplayer extends HtmlBrowser.URLDisplayer { 29 | 30 | @Override 31 | public void showURL(URL url) { 32 | RestClientTopComponent component = new RestClientTopComponent(); 33 | component.open(); 34 | component.setUrl(url.toString()); 35 | component.requestActive(); 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/com/javierllorente/netbeans/rest/client/http/editor/sidebar/request/IRequestProcessor.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 Christian Lenz . 3 | * 4 | * Licensed to the Apache Software Foundation (ASF) under one 5 | * or more contributor license agreements. See the NOTICE file 6 | * distributed with this work for additional information 7 | * regarding copyright ownership. The ASF licenses this file 8 | * to you under the Apache License, Version 2.0 (the 9 | * "License"); you may not use this file except in compliance 10 | * with the License. You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, 15 | * software distributed under the License is distributed on an 16 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 | * KIND, either express or implied. See the License for the 18 | * specific language governing permissions and limitations 19 | * under the License. 20 | */ 21 | package com.javierllorente.netbeans.rest.client.http.editor.sidebar.request; 22 | 23 | import com.javierllorente.netbeans.rest.client.http.editor.syntax.antlr.HTTPParser; 24 | import java.util.List; 25 | 26 | /** 27 | * 28 | * @author Christian Lenz 29 | */ 30 | public interface IRequestProcessor { 31 | 32 | List getRequests(); 33 | 34 | void updateRequestsList(String text); 35 | 36 | void callRequest(HTTPParser.RequestContext requestContext); 37 | 38 | void openRequestInUi(HTTPParser.RequestContext requestContext); 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/com/javierllorente/netbeans/rest/client/http/editor/sidebar/RunHttpRequestsSideBarPanel.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 Christian Lenz . 3 | * 4 | * Licensed to the Apache Software Foundation (ASF) under one 5 | * or more contributor license agreements. See the NOTICE file 6 | * distributed with this work for additional information 7 | * regarding copyright ownership. The ASF licenses this file 8 | * to you under the Apache License, Version 2.0 (the 9 | * "License"); you may not use this file except in compliance 10 | * with the License. You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, 15 | * software distributed under the License is distributed on an 16 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 | * KIND, either express or implied. See the License for the 18 | * specific language governing permissions and limitations 19 | * under the License. 20 | */ 21 | package com.javierllorente.netbeans.rest.client.http.editor.sidebar; 22 | 23 | import com.javierllorente.netbeans.rest.client.http.editor.sidebar.request.IRequestProcessor; 24 | import java.awt.BorderLayout; 25 | import javax.swing.JPanel; 26 | import javax.swing.text.JTextComponent; 27 | 28 | /** 29 | * Sidebar Panel to display task-related UI elements. 30 | */ 31 | public class RunHttpRequestsSideBarPanel extends JPanel { 32 | 33 | public RunHttpRequestsSideBarPanel(JTextComponent target, IRequestProcessor taskProcessor) { 34 | super(new BorderLayout()); 35 | add(new DrawingPanel(target, taskProcessor)); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/com/javierllorente/netbeans/rest/client/http/editor/sidebar/ResponseSideBarFactory.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 Christian Lenz . 3 | * 4 | * Licensed to the Apache Software Foundation (ASF) under one 5 | * or more contributor license agreements. See the NOTICE file 6 | * distributed with this work for additional information 7 | * regarding copyright ownership. The ASF licenses this file 8 | * to you under the Apache License, Version 2.0 (the 9 | * "License"); you may not use this file except in compliance 10 | * with the License. You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, 15 | * software distributed under the License is distributed on an 16 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 | * KIND, either express or implied. See the License for the 18 | * specific language governing permissions and limitations 19 | * under the License. 20 | */ 21 | package com.javierllorente.netbeans.rest.client.http.editor.sidebar; 22 | 23 | import javax.swing.JComponent; 24 | import javax.swing.text.JTextComponent; 25 | import org.netbeans.spi.editor.SideBarFactory; 26 | 27 | /** 28 | * Factory for creating the east sidebar that displays HTTP responses. 29 | * 30 | * @author Christian Lenz 31 | */ 32 | public class ResponseSideBarFactory implements SideBarFactory { 33 | 34 | @Override 35 | public JComponent createSideBar(JTextComponent target) { 36 | ResponseSidebarPanel panel = new ResponseSidebarPanel(target); 37 | 38 | ResponseSidebarManager.getInstance().registerSidebar(target, panel); 39 | 40 | return panel; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/com/javierllorente/netbeans/rest/client/http/editor/sidebar/RunHttpRequestsSideBarFactory.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 Christian Lenz . 3 | * 4 | * Licensed to the Apache Software Foundation (ASF) under one 5 | * or more contributor license agreements. See the NOTICE file 6 | * distributed with this work for additional information 7 | * regarding copyright ownership. The ASF licenses this file 8 | * to you under the Apache License, Version 2.0 (the 9 | * "License"); you may not use this file except in compliance 10 | * with the License. You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, 15 | * software distributed under the License is distributed on an 16 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 | * KIND, either express or implied. See the License for the 18 | * specific language governing permissions and limitations 19 | * under the License. 20 | */ 21 | package com.javierllorente.netbeans.rest.client.http.editor.sidebar; 22 | 23 | import com.javierllorente.netbeans.rest.client.http.editor.sidebar.request.RequestProcessor; 24 | import javax.swing.JComponent; 25 | import javax.swing.text.JTextComponent; 26 | import javax.swing.text.StyledDocument; 27 | import org.netbeans.spi.editor.SideBarFactory; 28 | 29 | public class RunHttpRequestsSideBarFactory implements SideBarFactory { 30 | 31 | @Override 32 | public JComponent createSideBar(JTextComponent target) { 33 | RequestProcessor processor = new RequestProcessor((StyledDocument) target.getDocument()); 34 | processor.setTextComponent(target); 35 | return new RunHttpRequestsSideBarPanel(target, processor); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/com/javierllorente/netbeans/rest/client/ui/StatusLabel.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 Javier Llorente . 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.javierllorente.netbeans.rest.client.ui; 17 | 18 | import java.awt.Dimension; 19 | import java.awt.Graphics; 20 | import javax.swing.JComponent; 21 | import javax.swing.JLabel; 22 | import javax.swing.JPanel; 23 | import javax.swing.SwingUtilities; 24 | import javax.swing.plaf.LayerUI; 25 | 26 | /** 27 | * 28 | * @author Javier Llorente 29 | */ 30 | public class StatusLabel extends LayerUI { 31 | 32 | private final JLabel label; 33 | private final JPanel panel; 34 | 35 | public StatusLabel() { 36 | super(); 37 | label = new JLabel(); 38 | panel = new JPanel(); 39 | } 40 | 41 | public void setText(String text) { 42 | label.setText(text); 43 | } 44 | 45 | @Override 46 | public void paint(Graphics g, JComponent c) { 47 | super.paint(g, c); 48 | Dimension d = label.getPreferredSize(); 49 | int x = c.getWidth() - d.width - 10; 50 | SwingUtilities.paintComponent(g, label, panel, x, 8, d.width, d.height); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/main/java/com/javierllorente/netbeans/rest/client/ui/actions/OpenInUIAction.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2025 Christian Lenz 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.javierllorente.netbeans.rest.client.ui.actions; 17 | 18 | import com.javierllorente.netbeans.rest.client.ui.RestClientTopComponent; 19 | import java.awt.event.ActionEvent; 20 | import java.awt.event.ActionListener; 21 | import org.openide.awt.ActionID; 22 | import org.openide.awt.ActionReference; 23 | import org.openide.awt.ActionRegistration; 24 | import org.openide.util.NbBundle.Messages; 25 | 26 | @ActionID( 27 | category = "Tools", 28 | id = "com.javierllorente.netbeans.rest.client.ui.actions.OpenInUIAction" 29 | ) 30 | @ActionRegistration( 31 | displayName = "#CTL_OpenInUIAction", 32 | iconBase = "com/javierllorente/netbeans/rest/client/restservice.png" 33 | ) 34 | @ActionReference(path = "Menu/Tools/RestClient", position = 100) 35 | @Messages("CTL_OpenInUIAction=Open in UI") 36 | public final class OpenInUIAction implements ActionListener { 37 | 38 | @Override 39 | public void actionPerformed(ActionEvent e) { 40 | RestClientTopComponent tc = new RestClientTopComponent(); 41 | tc.open(); 42 | tc.requestActive(); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/com/javierllorente/netbeans/rest/util/ExceptionUtils.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 Javier Llorente . 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.javierllorente.netbeans.rest.util; 17 | 18 | import com.javierllorente.netbeans.rest.client.ui.ResponsePanel; 19 | import jakarta.ws.rs.ProcessingException; 20 | import javax.swing.SwingUtilities; 21 | 22 | /** 23 | * 24 | * @author Javier Llorente 25 | */ 26 | public class ExceptionUtils { 27 | 28 | private ExceptionUtils() { 29 | } 30 | 31 | public static void handleAndDisplayProcessingException(ProcessingException ex, ResponsePanel responsePanel) { 32 | String response = (ex.getMessage().contains("PKIX path building failed")) 33 | ? "Could not get response: failed to verify SSL certificate\n" 34 | + "SSL certificate verification is enabled. " 35 | + "You may disable it under Tools->Options->Miscellaneous->REST Client" 36 | : ex.getMessage(); 37 | SwingUtilities.invokeLater(() -> { 38 | responsePanel.setContentType(""); 39 | responsePanel.setResponse(response); 40 | responsePanel.showResponse(); 41 | }); 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/com/javierllorente/netbeans/rest/client/UserAgent.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2022-2023 Javier Llorente 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.javierllorente.netbeans.rest.client; 17 | 18 | import org.openide.modules.Modules; 19 | 20 | /** 21 | * 22 | * @author Javier Llorente 23 | */ 24 | public class UserAgent { 25 | 26 | public final static String NAME = "RestClient"; 27 | public final static String VERSION = getVersion(); 28 | public final static String FULL = NAME + "/" + VERSION 29 | + " (" + System.getProperty("os.name") + " " 30 | + System.getProperty("os.version") + "; " 31 | + System.getProperty("os.arch") + ") " 32 | + System.getProperty("netbeans.productversion"); 33 | 34 | private UserAgent() { 35 | } 36 | 37 | private static String getVersion() { 38 | var module = Modules.getDefault().ownerOf(UserAgent.class); 39 | 40 | if (module == null) { 41 | return "unknown"; 42 | } 43 | 44 | var implementationVersion = module.getImplementationVersion(); 45 | 46 | if (implementationVersion == null) { 47 | return "unknown"; 48 | } 49 | 50 | return implementationVersion; 51 | } 52 | 53 | } 54 | -------------------------------------------------------------------------------- /src/main/java/com/javierllorente/netbeans/rest/client/http/editor/sidebar/request/Request.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 Christian Lenz . 3 | * 4 | * Licensed to the Apache Software Foundation (ASF) under one 5 | * or more contributor license agreements. See the NOTICE file 6 | * distributed with this work for additional information 7 | * regarding copyright ownership. The ASF licenses this file 8 | * to you under the Apache License, Version 2.0 (the 9 | * "License"); you may not use this file except in compliance 10 | * with the License. You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, 15 | * software distributed under the License is distributed on an 16 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 | * KIND, either express or implied. See the License for the 18 | * specific language governing permissions and limitations 19 | * under the License. 20 | */ 21 | package com.javierllorente.netbeans.rest.client.http.editor.sidebar.request; 22 | 23 | import com.javierllorente.netbeans.rest.client.http.editor.syntax.antlr.HTTPParser; 24 | 25 | /** 26 | * 27 | * @author Christian Lenz 28 | */ 29 | public class Request { 30 | 31 | private final HTTPParser.RequestContext requestContext; 32 | private final String requestLineText; 33 | private final int lineNumber; 34 | 35 | public Request(HTTPParser.RequestContext requestContext, String requestLineText, int lineNumber) { 36 | this.requestContext = requestContext; 37 | this.requestLineText = requestLineText; 38 | this.lineNumber = lineNumber; 39 | } 40 | 41 | public HTTPParser.RequestContext getrequestContext() { 42 | return requestContext; 43 | } 44 | 45 | public String getRequestLineText() { 46 | return requestLineText; 47 | } 48 | 49 | public int getLineNumber() { 50 | return lineNumber; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/main/java/com/javierllorente/netbeans/rest/client/http/editor/syntax/coloring/HTTPTokenId.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 Christian Lenz . 3 | * 4 | * Licensed to the Apache Software Foundation (ASF) under one 5 | * or more contributor license agreements. See the NOTICE file 6 | * distributed with this work for additional information 7 | * regarding copyright ownership. The ASF licenses this file 8 | * to you under the Apache License, Version 2.0 (the 9 | * "License"); you may not use this file except in compliance 10 | * with the License. You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, 15 | * software distributed under the License is distributed on an 16 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 | * KIND, either express or implied. See the License for the 18 | * specific language governing permissions and limitations 19 | * under the License. 20 | */ 21 | package com.javierllorente.netbeans.rest.client.http.editor.syntax.coloring; 22 | 23 | import org.netbeans.api.lexer.TokenId; 24 | 25 | /** 26 | * 27 | * @author Christian Lenz 28 | */ 29 | public enum HTTPTokenId implements TokenId { 30 | 31 | METHOD("method"), 32 | URL("url"), 33 | DIGITS("number"), 34 | REQUEST_SEPARATOR("request_separator"), 35 | NONE("none"), 36 | COMMENT("comment"), 37 | WS("whitespace"), 38 | HTTP_PROTOCOL("http_protocol"), 39 | HTTP_VERSION("http_version"), 40 | HEADER_KEY("header_key"), 41 | HEADER_VALUE("header_value"); 42 | 43 | private final String primaryCategory; 44 | 45 | HTTPTokenId(String primaryCategory) { 46 | this.primaryCategory = primaryCategory; 47 | } 48 | 49 | @Override 50 | public String primaryCategory() { 51 | return primaryCategory; 52 | } 53 | 54 | public static final String MIME_TYPE = "text/x-http"; // NOI18N 55 | } 56 | -------------------------------------------------------------------------------- /src/main/java/com/javierllorente/netbeans/rest/client/event/UrlDocumentListener.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 Javier Llorente . 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.javierllorente.netbeans.rest.client.event; 17 | 18 | import com.javierllorente.netbeans.rest.client.parsers.UrlParamsParser; 19 | import com.javierllorente.netbeans.rest.client.ui.TablePanel; 20 | import javax.swing.event.DocumentEvent; 21 | import javax.swing.event.DocumentListener; 22 | import javax.swing.text.BadLocationException; 23 | import org.openide.util.Exceptions; 24 | 25 | /** 26 | * 27 | * @author Javier Llorente 28 | */ 29 | public class UrlDocumentListener implements DocumentListener { 30 | 31 | private final UrlParamsParser parser; 32 | 33 | public UrlDocumentListener(TablePanel paramsPanel) { 34 | parser = new UrlParamsParser(paramsPanel); 35 | } 36 | 37 | @Override 38 | public void insertUpdate(DocumentEvent de) { 39 | try { 40 | String urlFieldUpdate = de.getDocument().getText(0, de.getDocument().getLength()); 41 | parser.processChanges(urlFieldUpdate); 42 | } catch (BadLocationException ex) { 43 | Exceptions.printStackTrace(ex); 44 | } 45 | } 46 | 47 | @Override 48 | public void removeUpdate(DocumentEvent de) { 49 | try { 50 | String urlFieldUpdate = de.getDocument().getText(0, de.getDocument().getLength()); 51 | parser.processChanges(urlFieldUpdate); 52 | } catch (BadLocationException ex) { 53 | Exceptions.printStackTrace(ex); 54 | } 55 | } 56 | 57 | @Override 58 | public void changedUpdate(DocumentEvent de) { 59 | 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/main/java/com/javierllorente/netbeans/rest/client/event/CellDocumentListener.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 Javier Llorente . 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.javierllorente.netbeans.rest.client.event; 17 | 18 | import com.javierllorente.netbeans.rest.client.parsers.CellParamsParser; 19 | import javax.swing.event.DocumentEvent; 20 | import javax.swing.event.DocumentListener; 21 | import javax.swing.text.BadLocationException; 22 | import org.openide.util.Exceptions; 23 | 24 | /** 25 | * 26 | * @author Javier Llorente 27 | */ 28 | public class CellDocumentListener implements DocumentListener { 29 | 30 | private final CellParamsParser parser; 31 | private String cellText; 32 | 33 | public CellDocumentListener(CellParamsParser parser) { 34 | this.parser = parser; 35 | } 36 | 37 | @Override 38 | public void insertUpdate(DocumentEvent de) { 39 | try { 40 | cellText = getDocumentText(de); 41 | parser.processChanges(cellText); 42 | } catch (BadLocationException ex) { 43 | Exceptions.printStackTrace(ex); 44 | } 45 | } 46 | 47 | @Override 48 | public void removeUpdate(DocumentEvent de) { 49 | try { 50 | cellText = getDocumentText(de); 51 | parser.processChanges(cellText); 52 | } catch (BadLocationException ex) { 53 | Exceptions.printStackTrace(ex); 54 | } 55 | } 56 | 57 | @Override 58 | public void changedUpdate(DocumentEvent de) { 59 | } 60 | 61 | private String getDocumentText(DocumentEvent de) throws BadLocationException { 62 | return de.getDocument().getText(0, de.getDocument().getLength()); 63 | } 64 | 65 | public String getCellText() { 66 | return cellText; 67 | } 68 | 69 | } 70 | -------------------------------------------------------------------------------- /src/main/java/com/javierllorente/netbeans/rest/client/ui/actions/OpenInEditorAction.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2025 Christian Lenz 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.javierllorente.netbeans.rest.client.ui.actions; 17 | 18 | import com.javierllorente.netbeans.rest.client.UserAgent; 19 | import com.javierllorente.netbeans.rest.client.util.HttpFileUtils; 20 | import java.awt.event.ActionEvent; 21 | import java.awt.event.ActionListener; 22 | import java.io.IOException; 23 | import org.openide.awt.ActionID; 24 | import org.openide.awt.ActionReference; 25 | import org.openide.awt.ActionRegistration; 26 | import org.openide.util.Exceptions; 27 | import org.openide.util.NbBundle.Messages; 28 | 29 | @ActionID( 30 | category = "Tools", 31 | id = "com.javierllorente.netbeans.rest.client.ui.actions.OpenInEditorAction" 32 | ) 33 | @ActionRegistration( 34 | displayName = "#CTL_OpenInEditorAction", 35 | iconBase = "com/javierllorente/netbeans/rest/client/http/editor/http.png" 36 | ) 37 | @ActionReference(path = "Menu/Tools/RestClient", position = 200) 38 | @Messages("CTL_OpenInEditorAction=Open in Editor") 39 | public final class OpenInEditorAction implements ActionListener { 40 | 41 | @Override 42 | public void actionPerformed(ActionEvent e) { 43 | try { 44 | // Create example HTTP request content 45 | String httpContent = "### GET request to example petstore swagger server\n" 46 | + "GET https://petstore.swagger.io/v2/pet/findByStatus?status=available\n" 47 | + "User-Agent: " + UserAgent.FULL + "\n" 48 | + "\n" 49 | + "###\n"; 50 | HttpFileUtils.createAndOpenHttpFile(httpContent, 0); 51 | } catch (IOException ex) { 52 | Exceptions.printStackTrace(ex); 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/main/java/com/javierllorente/netbeans/rest/client/event/TabChangeListener.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 Javier Llorente . 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.javierllorente.netbeans.rest.client.event; 17 | 18 | import com.javierllorente.netbeans.rest.client.ui.AuthPanel; 19 | import com.javierllorente.netbeans.rest.client.ui.TablePanel; 20 | import javax.swing.JTabbedPane; 21 | import javax.swing.event.ChangeEvent; 22 | import javax.swing.event.ChangeListener; 23 | import javax.swing.event.DocumentListener; 24 | 25 | /** 26 | * 27 | * @author Javier Llorente 28 | */ 29 | public class TabChangeListener implements ChangeListener { 30 | 31 | private final int AUTHORISATION_TAB = 1; 32 | 33 | private final TablePanel headersPanel; 34 | private final AuthPanel authPanel; 35 | private final DocumentListener tokenDocumentListener; 36 | 37 | public TabChangeListener(TablePanel headersPanel, AuthPanel authPanel, 38 | DocumentListener tokenDocumentListener) { 39 | this.headersPanel = headersPanel; 40 | this.authPanel = authPanel; 41 | this.tokenDocumentListener = tokenDocumentListener; 42 | } 43 | 44 | @Override 45 | public void stateChanged(ChangeEvent ce) { 46 | JTabbedPane pane = (JTabbedPane) ce.getSource(); 47 | if (pane.getSelectedIndex() == AUTHORISATION_TAB) { 48 | int index = headersPanel.containsKey("Authorization"); 49 | String token = ""; 50 | 51 | if (index != -1 && headersPanel.getValue(index).startsWith("Bearer")) { 52 | token = headersPanel.getValue(index).replaceFirst("Bearer ", ""); 53 | } 54 | 55 | authPanel.removeTokenDocumentListener(tokenDocumentListener); 56 | authPanel.setToken(token); 57 | authPanel.addTokenDocumentListener(tokenDocumentListener); 58 | } 59 | } 60 | 61 | } 62 | -------------------------------------------------------------------------------- /src/main/java/com/javierllorente/netbeans/rest/client/http/editor/syntax/HTTPLangLexer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 Christian Lenz . 3 | * 4 | * Licensed to the Apache Software Foundation (ASF) under one 5 | * or more contributor license agreements. See the NOTICE file 6 | * distributed with this work for additional information 7 | * regarding copyright ownership. The ASF licenses this file 8 | * to you under the Apache License, Version 2.0 (the 9 | * "License"); you may not use this file except in compliance 10 | * with the License. You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, 15 | * software distributed under the License is distributed on an 16 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 | * KIND, either express or implied. See the License for the 18 | * specific language governing permissions and limitations 19 | * under the License. 20 | */ 21 | package com.javierllorente.netbeans.rest.client.http.editor.syntax; 22 | 23 | import com.javierllorente.netbeans.rest.client.http.editor.syntax.antlr.HTTPLexer; 24 | import com.javierllorente.netbeans.rest.client.http.editor.syntax.coloring.HTTPTokenId; 25 | import org.netbeans.api.lexer.Token; 26 | import org.netbeans.spi.lexer.LexerRestartInfo; 27 | import org.netbeans.spi.lexer.antlr4.AbstractAntlrLexerBridge; 28 | 29 | /** 30 | * 31 | * @author Christian Lenz 32 | */ 33 | public final class HTTPLangLexer extends AbstractAntlrLexerBridge { 34 | 35 | public HTTPLangLexer(LexerRestartInfo info) { 36 | super(info, HTTPLexer::new); 37 | } 38 | 39 | @Override 40 | protected Token mapToken(org.antlr.v4.runtime.Token antlrToken) { 41 | switch (antlrToken.getType()) { 42 | case HTTPLexer.METHOD: 43 | return token(HTTPTokenId.METHOD); 44 | case HTTPLexer.COMMENT: 45 | return token(HTTPTokenId.COMMENT); 46 | case HTTPLexer.REQUEST_SEPARATOR: 47 | return token(HTTPTokenId.REQUEST_SEPARATOR); 48 | case HTTPLexer.DIGITS: 49 | return token(HTTPTokenId.DIGITS); 50 | case HTTPLexer.WS: 51 | return token(HTTPTokenId.WS); 52 | case HTTPLexer.HTTP_PROTOCOL: 53 | return token(HTTPTokenId.HTTP_PROTOCOL); 54 | 55 | default: 56 | return token(HTTPTokenId.NONE); 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/main/java/com/javierllorente/netbeans/rest/client/event/TokenDocumentListener.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 Javier Llorente . 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.javierllorente.netbeans.rest.client.event; 17 | 18 | import com.javierllorente.netbeans.rest.client.ui.TablePanel; 19 | import javax.swing.event.DocumentEvent; 20 | import javax.swing.event.DocumentListener; 21 | import javax.swing.text.BadLocationException; 22 | import org.openide.util.Exceptions; 23 | 24 | /** 25 | * 26 | * @author Javier Llorente 27 | */ 28 | public class TokenDocumentListener implements DocumentListener { 29 | 30 | private final TablePanel headersPanel; 31 | 32 | public TokenDocumentListener(TablePanel headersPanel) { 33 | this.headersPanel = headersPanel; 34 | } 35 | 36 | private String getDocumentText(DocumentEvent de) throws BadLocationException { 37 | return de.getDocument().getText(0, de.getDocument().getLength()); 38 | } 39 | 40 | @Override 41 | public void insertUpdate(DocumentEvent de) { 42 | try { 43 | int index = headersPanel.containsKey("Authorization"); 44 | if (index != -1) { 45 | headersPanel.editRow(index, "Authorization", "Bearer " + getDocumentText(de)); 46 | } else { 47 | headersPanel.insertRow(0, "Authorization", "Bearer " + getDocumentText(de)); 48 | } 49 | } catch (BadLocationException ex) { 50 | Exceptions.printStackTrace(ex); 51 | } 52 | } 53 | 54 | @Override 55 | public void removeUpdate(DocumentEvent de) { 56 | try { 57 | int index = headersPanel.containsKey("Authorization"); 58 | if (index != -1) { 59 | headersPanel.editRow(index, "Authorization", "Bearer " + getDocumentText(de)); 60 | } else { 61 | headersPanel.insertRow(0, "Authorization", "Bearer " + getDocumentText(de)); 62 | } 63 | } catch (BadLocationException ex) { 64 | Exceptions.printStackTrace(ex); 65 | } 66 | } 67 | 68 | @Override 69 | public void changedUpdate(DocumentEvent de) { 70 | } 71 | 72 | } 73 | -------------------------------------------------------------------------------- /src/main/java/com/javierllorente/netbeans/rest/client/http/editor/sidebar/ActionPopup.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 Christian Lenz . 3 | * 4 | * Licensed to the Apache Software Foundation (ASF) under one 5 | * or more contributor license agreements. See the NOTICE file 6 | * distributed with this work for additional information 7 | * regarding copyright ownership. The ASF licenses this file 8 | * to you under the Apache License, Version 2.0 (the 9 | * "License"); you may not use this file except in compliance 10 | * with the License. You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, 15 | * software distributed under the License is distributed on an 16 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 | * KIND, either express or implied. See the License for the 18 | * specific language governing permissions and limitations 19 | * under the License. 20 | */ 21 | package com.javierllorente.netbeans.rest.client.http.editor.sidebar; 22 | 23 | import com.javierllorente.netbeans.rest.client.http.editor.sidebar.request.IRequestProcessor; 24 | import com.javierllorente.netbeans.rest.client.http.editor.sidebar.request.Request; 25 | import java.awt.event.ActionEvent; 26 | import javax.swing.Icon; 27 | import javax.swing.JMenuItem; 28 | import javax.swing.JPopupMenu; 29 | import org.openide.util.ImageUtilities; 30 | 31 | /** 32 | * Popup menu for running and debugging tasks. 33 | */ 34 | public class ActionPopup { 35 | 36 | private final IRequestProcessor requestProcessor; 37 | 38 | public ActionPopup(IRequestProcessor requestProcessor) { 39 | this.requestProcessor = requestProcessor; 40 | } 41 | 42 | public JPopupMenu createPopupMenu(Request request) { 43 | Icon runIcon = ImageUtilities.loadImageIcon("org/netbeans/modules/project/ui/resources/runProject.png", false); 44 | Icon uiIcon = ImageUtilities.loadImageIcon("com/javierllorente/netbeans/rest/client/restservice.png", false); 45 | 46 | JPopupMenu popupMenu = new JPopupMenu(); 47 | 48 | // Call action 49 | JMenuItem runItem = new JMenuItem("Call '" + request.getRequestLineText(), runIcon); 50 | runItem.addActionListener((ActionEvent e) -> { 51 | System.out.println("Call " + request.getRequestLineText()); 52 | requestProcessor.callRequest(request.getrequestContext()); 53 | }); 54 | popupMenu.add(runItem); 55 | 56 | // Open in UI action 57 | JMenuItem openInUIItem = new JMenuItem("Open in UI", uiIcon); 58 | openInUIItem.addActionListener((ActionEvent e) -> { 59 | requestProcessor.openRequestInUi(request.getrequestContext()); 60 | }); 61 | popupMenu.add(openInUIItem); 62 | 63 | return popupMenu; 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/main/java/com/javierllorente/netbeans/rest/client/parsers/CellParamsParser.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 Javier Llorente . 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.javierllorente.netbeans.rest.client.parsers; 17 | 18 | import com.javierllorente.netbeans.rest.client.ui.UrlPanel; 19 | import com.javierllorente.netbeans.rest.client.ui.TablePanel; 20 | import java.util.Arrays; 21 | import java.util.List; 22 | import java.util.logging.Level; 23 | import java.util.logging.Logger; 24 | import javax.swing.event.DocumentListener; 25 | 26 | /** 27 | * 28 | * @author Javier Llorente 29 | */ 30 | public class CellParamsParser { 31 | 32 | private static final Logger logger = Logger.getLogger(CellParamsParser.class.getName()); 33 | private final TablePanel paramsPanel; 34 | private final UrlPanel urlPanel; 35 | private final DocumentListener urlDocumentListener; 36 | 37 | public CellParamsParser(TablePanel paramsPanel, UrlPanel urlPanel, 38 | DocumentListener urlDocumentListener) { 39 | this.paramsPanel = paramsPanel; 40 | this.urlPanel = urlPanel; 41 | this.urlDocumentListener = urlDocumentListener; 42 | } 43 | 44 | public void processChanges(String cellText) { 45 | logger.log(Level.INFO, "processChanges() cellText = {0}", cellText); 46 | String params = "?"; 47 | 48 | for (int i = 0; i < paramsPanel.getRowCount(); i++) { 49 | 50 | if (i != paramsPanel.getSelectedRow()) { 51 | params += paramsPanel.getKey(i) + "=" + paramsPanel.getValue(i); 52 | } else { 53 | params += (paramsPanel.getSelectedColumn() == 1) 54 | ? cellText + "=" + paramsPanel.getValue(i) 55 | : paramsPanel.getKey(i) + "=" + cellText; 56 | } 57 | 58 | if (i != paramsPanel.getRowCount() - 1) { 59 | params += "&"; 60 | } 61 | } 62 | 63 | List url = Arrays.asList(urlPanel.getUrl().split("\\?", 2)); 64 | urlPanel.removeUrlDocumentListener(urlDocumentListener); 65 | urlPanel.setUrl(url.get(0) + params); 66 | urlPanel.addUrlDocumentListener(urlDocumentListener); 67 | 68 | logger.log(Level.INFO, "url = {0}", url); 69 | logger.log(Level.INFO, "params = {0}", params); 70 | } 71 | 72 | } 73 | -------------------------------------------------------------------------------- /test-stuff/test.http: -------------------------------------------------------------------------------- 1 | GET test.de 2 | ### 3 | das 4 | ll: test 5 | ### 6 | 7 | ### (ohne HTTP-Version) 8 | GET / 9 | 10 | ### Test 11 | 12 | # HTTP/1.0 Beispiele (mit expliziter Version) 13 | GET / HTTP/1.0 14 | 15 | ### 16 | GET /page.html HTTP/1.0 17 | 18 | # HTTP/1.1 – origin-form (nur Pfad und Query) 19 | ### 20 | GET / HTTP/1.1 21 | GET /index.html HTTP/1.1 22 | GET /folder/ HTTP/1.1 23 | GET http://google.de/search?q=test HTTP/1.1 24 | GET google.de?q=test HTTP/1.1 25 | GET google.de?q1=test HTTP/1.1 26 | GET google.de/?q=test HTTP/1.1 27 | GET /search?qquery=test HTTP/1.1 28 | GET /search/?q=test HTTP/1.1 29 | DELETE wiki.de/? HTTP/1.1 30 | DELETE wiki.de? HTTP/1.1 31 | 32 | ### Seperator 33 | 34 | # HTTP/1.1 – absolute-form (für Proxy-Anfragen) 35 | GET http://examplemple.com HTTP/1.1 36 | GET https://example.com/path?query=string HTTP/1.1 37 | 38 | # Verschiedene HTTP-Methoden 39 | PUT /api/resource HTTP/1.1 40 | POST /submit HTTP/1.1 41 | DELETE /api/resource/123 HTTP/1.1 42 | OPTIONS / HTTP/1.1 43 | HEAD / HTTP/1.1 44 | TRACE /debug HTTP/1.1 45 | 46 | # Asterisk-Form (zum Beispiel bei OPTIONS) 47 | OPTIONS * HTTP/1.1 48 | 49 | # WebSocket-Anfragen (WS/WSS) 50 | GET ws://echo.websocket.org/ HTTP/1.1 51 | GET wss://echo.websocket.org/ HTTP/1.1 52 | 53 | 54 | # Komplexere Beispiele mit Query-Parametern und URL-Encoding 55 | GET /search?q=test%20query&lang=de HTTP/1.1 56 | PUT /update?item=42&status=active HTTP/1.1 57 | GET /api/dat?param=value1¶m2=value2 HTTP/1.1 58 | GET /search?query=%3Cscript%3E HTTP/1.1 59 | 60 | 61 | GET google.de HTTP/1.1 62 | GET google.de HTTP /2 63 | GET http://google.de HTTP/ 3 64 | GET www.x.com HTTP1 65 | GET google.de HTTP 66 | POST twitter.de HTTP/2d 67 | DELETE wiki.de/ HTTP/1.1 68 | DELETE wiki.de/123 HTTP/1.1 69 | DELETE http://wiki.de/ HTTP/1.1 70 | DELETE http://www.wiki.de/ HTTP/1.1 71 | GET /api/ HTTP/1.1 72 | GET / HTTP/1.1 73 | GET google.de/index.html HTTP/1.1 74 | GET /api/test HTTP/1.1 75 | GET /api/test/ HTTP/1.1 76 | GET /api/test/estset HTTP/1.1 77 | GET /api HTTP/1.1 78 | GET /1 HTTP/1.1 79 | GET /v1 HTTP/1.1 80 | 81 | GET wiki.de:443 HTTP/1.1 82 | GET http://example.com:8080 HTTP/1.1 83 | GET https://example.com:8080 HTTP/1.1 84 | GET http://example.com:8080/test HTTP/1.1 85 | 86 | GET http://192.168.1.1 87 | GET 192.168.1.1:443 HTTP/2.1 88 | GET 192.168.1.1 HTTP/1.1 89 | GET 192.168.1.1/ HTTP/1.1 90 | GET 0.0.0.0 HTTP/2 91 | GET 127.0.0.1 HTTP/2 92 | CONNECT example.com:443 93 | 94 | GET http://[2001:db8::1]/ HTTP/1.1 95 | GET http://[2001:db8::2]:8080/ HTTP/1.1 96 | GET http://[2001:db8::3]/search?q=test HTTP/1.1 97 | GET http://[2001:0db8:85a3::8a2e:0370:7334]/index.html HTTP/1.1 98 | GET http://[::1]/ HTTP/1.1 99 | GET http://[fe80::1ff:fe23:4567:890a]/docs HTTP/1.1 100 | GET http://[2001:db8:1234:5678:9abc:def0:1234:5678]:8443/settings HTTP/1.1 101 | GET http://[2001:db8::1]/ HTTP/1.1 102 | GET http://[2001:db8::1:3]:443 HTTP/1.1 103 | GET http://[2001:db8::1:3]:443 HTTP/1.1 104 | GET [2001:db8::1] HTTP/1.1 105 | GET [2001:db8::1]:443 HTTP/1.1 106 | GET [::1] HTTP/1.1 107 | GET [::] HTTP/1.1 108 | GET [::1]:80 HTTP/1.1 -------------------------------------------------------------------------------- /src/main/resources/com/javierllorente/netbeans/rest/client/layer.xml: -------------------------------------------------------------------------------- 1 | 2 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | -------------------------------------------------------------------------------- /src/main/java/com/javierllorente/netbeans/rest/client/http/editor/syntax/HTTPLangParser.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 Christian Lenz . 3 | * 4 | * Licensed to the Apache Software Foundation (ASF) under one 5 | * or more contributor license agreements. See the NOTICE file 6 | * distributed with this work for additional information 7 | * regarding copyright ownership. The ASF licenses this file 8 | * to you under the Apache License, Version 2.0 (the 9 | * "License"); you may not use this file except in compliance 10 | * with the License. You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, 15 | * software distributed under the License is distributed on an 16 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 | * KIND, either express or implied. See the License for the 18 | * specific language governing permissions and limitations 19 | * under the License. 20 | */ 21 | package com.javierllorente.netbeans.rest.client.http.editor.syntax; 22 | 23 | import javax.swing.event.ChangeListener; 24 | import org.netbeans.modules.parsing.api.Snapshot; 25 | import org.netbeans.modules.parsing.api.Task; 26 | import org.netbeans.modules.parsing.spi.ParseException; 27 | import org.netbeans.modules.parsing.spi.Parser; 28 | import org.netbeans.modules.parsing.spi.SourceModificationEvent; 29 | 30 | /** 31 | * 32 | * @author Christian Lenz 33 | */ 34 | /** 35 | * HTTPLangParser is the NetBeans Parser class that ties the snapshot to our 36 | * {@link HTTPLangParserResult}. 37 | * 38 | *

39 | * Whenever NetBeans decides a parse is needed, it calls 40 | * {@link #parse(Snapshot, Task, SourceModificationEvent)}. We then build a new 41 | * {@link HTTPLangParserResult}, run its parse() method, and store it in 42 | * {@code lastResult}.

43 | */ 44 | public class HTTPLangParser extends Parser { 45 | 46 | private Result lastResult; 47 | 48 | /** 49 | * This is invoked by the NetBeans parsing API to parse the snapshot. 50 | * 51 | * @param snapshot The current snapshot of the file's contents. 52 | * @param task The parsing task (unused in this simple example). 53 | * @param event SourceModificationEvent with modification info (unused 54 | * here). 55 | * @throws ParseException if something goes wrong 56 | */ 57 | @Override 58 | public void parse(Snapshot snapshot, Task task, SourceModificationEvent event) throws ParseException { 59 | // Build a new ParserResult and parse the content 60 | lastResult = new HTTPLangParserResult(snapshot).parse(); 61 | } 62 | 63 | /** 64 | * Provides the last parser result created by 65 | * {@link #parse(Snapshot, Task, SourceModificationEvent)}. 66 | * 67 | * @param task The task requesting the result. 68 | * @return The most recent ParserResult for this snapshot. 69 | * @throws ParseException if something fails 70 | */ 71 | @Override 72 | public Result getResult(Task task) throws ParseException { 73 | return lastResult; 74 | } 75 | 76 | @Override 77 | public void addChangeListener(ChangeListener changeListener) { 78 | // Not used in this minimal example 79 | } 80 | 81 | @Override 82 | public void removeChangeListener(ChangeListener changeListener) { 83 | // Not used in this minimal example 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/main/java/com/javierllorente/netbeans/rest/client/event/TableParamsListener.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 Javier Llorente . 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.javierllorente.netbeans.rest.client.event; 17 | 18 | import com.javierllorente.netbeans.rest.client.ui.UrlPanel; 19 | import com.javierllorente.netbeans.rest.client.ui.TablePanel; 20 | import java.util.Arrays; 21 | import java.util.List; 22 | import java.util.logging.Level; 23 | import java.util.logging.Logger; 24 | import javax.swing.event.DocumentListener; 25 | import javax.swing.event.TableModelEvent; 26 | import javax.swing.event.TableModelListener; 27 | 28 | /** 29 | * 30 | * @author Javier Llorente 31 | */ 32 | public class TableParamsListener implements TableModelListener { 33 | 34 | private static final Logger logger = Logger.getLogger(TableParamsListener.class.getName()); 35 | private final TablePanel paramsPanel; 36 | private final UrlPanel urlPanel; 37 | private final DocumentListener urlDocumentListener; 38 | 39 | public TableParamsListener(TablePanel paramsPanel, UrlPanel urlPanel, 40 | DocumentListener urlDocumentListener) { 41 | this.paramsPanel = paramsPanel; 42 | this.urlPanel = urlPanel; 43 | this.urlDocumentListener = urlDocumentListener; 44 | } 45 | 46 | @Override 47 | public void tableChanged(TableModelEvent tme) { 48 | if (tme.getType() == TableModelEvent.INSERT || tme.getType() == TableModelEvent.DELETE) { 49 | updateUrlField(); 50 | } 51 | } 52 | 53 | private void updateUrlField() { 54 | // urlField.setText() invokes urlDocumentListener.removeUpdate() with 55 | // an empty text in the DocumentEvent, having all rows removed 56 | // even if the urlField is not empty. 57 | // Removing the listener avoids this situation 58 | logger.log(Level.INFO, "urlField = {0}", urlPanel.getUrl()); 59 | urlPanel.removeUrlDocumentListener(urlDocumentListener); 60 | urlPanel.setUrl(getUrl() + getParams()); 61 | urlPanel.addUrlDocumentListener(urlDocumentListener); 62 | 63 | logger.log(Level.INFO, "updated urlField = {0}", urlPanel.getUrl()); 64 | } 65 | 66 | private String getUrl() { 67 | List url = Arrays.asList(urlPanel.getUrl().split("\\?", 2)); 68 | return url.get(0); 69 | } 70 | 71 | private String getParams() { 72 | String params = paramsPanel.getRowCount() > 0 ? "?" : ""; 73 | 74 | for (int i = 0; i < paramsPanel.getRowCount(); i++) { 75 | params += paramsPanel.getKey(i) + "=" + paramsPanel.getValue(i); 76 | if (i != paramsPanel.getRowCount() - 1) { 77 | params += "&"; 78 | } 79 | } 80 | 81 | return params; 82 | } 83 | 84 | } 85 | -------------------------------------------------------------------------------- /src/main/java/com/javierllorente/netbeans/rest/client/ui/RestClientOptionsOptionsPanelController.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 Javier Llorente . 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.javierllorente.netbeans.rest.client.ui; 17 | 18 | import java.beans.PropertyChangeListener; 19 | import java.beans.PropertyChangeSupport; 20 | import javax.swing.JComponent; 21 | import javax.swing.SwingUtilities; 22 | import org.netbeans.spi.options.OptionsPanelController; 23 | import org.openide.util.HelpCtx; 24 | import org.openide.util.Lookup; 25 | 26 | @OptionsPanelController.SubRegistration( 27 | displayName = "#AdvancedOption_DisplayName_RestClientOptions", 28 | keywords = "#AdvancedOption_Keywords_RestClientOptions", 29 | keywordsCategory = "Advanced/RestClientOptions" 30 | ) 31 | @org.openide.util.NbBundle.Messages({"AdvancedOption_DisplayName_RestClientOptions=REST Client", 32 | "AdvancedOption_Keywords_RestClientOptions=rest"}) 33 | public final class RestClientOptionsOptionsPanelController extends OptionsPanelController { 34 | 35 | private RestClientOptionsPanel panel; 36 | private final PropertyChangeSupport pcs = new PropertyChangeSupport(this); 37 | private boolean changed; 38 | 39 | @Override 40 | public void update() { 41 | getPanel().load(); 42 | changed = false; 43 | } 44 | 45 | @Override 46 | public void applyChanges() { 47 | SwingUtilities.invokeLater(new Runnable() { 48 | @Override 49 | public void run() { 50 | getPanel().store(); 51 | changed = false; 52 | } 53 | }); 54 | } 55 | 56 | @Override 57 | public void cancel() { 58 | // need not do anything special, if no changes have been persisted yet 59 | } 60 | 61 | @Override 62 | public boolean isValid() { 63 | return getPanel().valid(); 64 | } 65 | 66 | @Override 67 | public boolean isChanged() { 68 | return changed; 69 | } 70 | 71 | @Override 72 | public HelpCtx getHelpCtx() { 73 | return null; // new HelpCtx("...ID") if you have a help set 74 | } 75 | 76 | @Override 77 | public JComponent getComponent(Lookup masterLookup) { 78 | return getPanel(); 79 | } 80 | 81 | @Override 82 | public void addPropertyChangeListener(PropertyChangeListener l) { 83 | pcs.addPropertyChangeListener(l); 84 | } 85 | 86 | @Override 87 | public void removePropertyChangeListener(PropertyChangeListener l) { 88 | pcs.removePropertyChangeListener(l); 89 | } 90 | 91 | private RestClientOptionsPanel getPanel() { 92 | if (panel == null) { 93 | panel = new RestClientOptionsPanel(this); 94 | } 95 | return panel; 96 | } 97 | 98 | void changed() { 99 | if (!changed) { 100 | changed = true; 101 | pcs.firePropertyChange(OptionsPanelController.PROP_CHANGED, false, true); 102 | } 103 | pcs.firePropertyChange(OptionsPanelController.PROP_VALID, null, null); 104 | } 105 | 106 | } 107 | -------------------------------------------------------------------------------- /src/main/java/com/javierllorente/netbeans/rest/client/ui/UrlPanel.form: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 |
72 | -------------------------------------------------------------------------------- /src/main/java/com/javierllorente/netbeans/rest/client/util/HttpFileUtils.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2025 Christian Lenz 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.javierllorente.netbeans.rest.client.util; 17 | 18 | import java.io.File; 19 | import java.io.IOException; 20 | import java.nio.file.Files; 21 | import java.text.SimpleDateFormat; 22 | import java.util.Date; 23 | import javax.swing.JEditorPane; 24 | import javax.swing.SwingUtilities; 25 | import javax.swing.text.StyledDocument; 26 | import org.openide.cookies.EditorCookie; 27 | import org.openide.cookies.OpenCookie; 28 | import org.openide.filesystems.FileObject; 29 | import org.openide.filesystems.FileUtil; 30 | import org.openide.loaders.DataObject; 31 | 32 | /** 33 | * Utility class for creating and opening .http files in the editor. 34 | */ 35 | public class HttpFileUtils { 36 | 37 | private HttpFileUtils() { 38 | // Utility class 39 | } 40 | 41 | /** 42 | * Creates a new .http file with the given content and opens it in the editor. 43 | * 44 | * @param httpContent The HTTP request content to write to the file 45 | * @param caretPosition The position where to place the cursor, or -1 to place it at the end 46 | * @throws IOException if file creation or opening fails 47 | */ 48 | public static void createAndOpenHttpFile(String httpContent, int caretPosition) throws IOException { 49 | if (httpContent == null) { 50 | httpContent = ""; 51 | } 52 | 53 | // Create .http file inside netbeans-rest-client folder in user directory 54 | File userDir = new File(System.getProperty("user.home"), ".netbeans/netbeans-rest-client"); 55 | userDir.mkdirs(); 56 | String timestamp = new SimpleDateFormat("yyyyMMdd'T'HHmmss").format(new Date()); 57 | File httpFile = new File(userDir, "request-" + timestamp + ".http"); 58 | Files.writeString(httpFile.toPath(), httpContent); 59 | 60 | // Open the file in the editor 61 | FileObject fileObject = FileUtil.toFileObject(httpFile); 62 | if (fileObject != null) { 63 | DataObject dataObject = DataObject.find(fileObject); 64 | OpenCookie openCookie = dataObject.getLookup().lookup(OpenCookie.class); 65 | EditorCookie editor = dataObject.getLookup().lookup(EditorCookie.class); 66 | 67 | if (openCookie == null) { 68 | return; 69 | } 70 | 71 | openCookie.open(); 72 | editor.openDocument(); 73 | 74 | // Position cursor if specified 75 | if (caretPosition >= 0) { 76 | final int finalCaretPosition = caretPosition; 77 | 78 | SwingUtilities.invokeLater(() -> { 79 | StyledDocument doc = editor.getDocument(); 80 | if (doc == null) { 81 | return; 82 | } 83 | 84 | JEditorPane[] panes = editor.getOpenedPanes(); 85 | if (panes == null || panes.length == 0) { 86 | return; 87 | } 88 | 89 | int pos = Math.min(finalCaretPosition, doc.getLength()); 90 | panes[0].setCaretPosition(pos); 91 | }); 92 | } 93 | } 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /src/main/java/com/javierllorente/netbeans/rest/client/http/editor/navigator/HTTPLangStructureScanner.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 Christian Lenz . 3 | * 4 | * Licensed to the Apache Software Foundation (ASF) under one 5 | * or more contributor license agreements. See the NOTICE file 6 | * distributed with this work for additional information 7 | * regarding copyright ownership. The ASF licenses this file 8 | * to you under the Apache License, Version 2.0 (the 9 | * "License"); you may not use this file except in compliance 10 | * with the License. You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, 15 | * software distributed under the License is distributed on an 16 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 | * KIND, either express or implied. See the License for the 18 | * specific language governing permissions and limitations 19 | * under the License. 20 | */ 21 | package com.javierllorente.netbeans.rest.client.http.editor.navigator; 22 | 23 | import com.javierllorente.netbeans.rest.client.http.editor.syntax.HTTPLangParserResult; 24 | import com.javierllorente.netbeans.rest.client.http.editor.syntax.antlr.HTTPParser; 25 | import java.util.ArrayList; 26 | import java.util.Collections; 27 | import java.util.List; 28 | import java.util.Map; 29 | import org.netbeans.modules.csl.api.OffsetRange; 30 | import org.netbeans.modules.csl.api.StructureItem; 31 | import org.netbeans.modules.csl.api.StructureScanner; 32 | import org.netbeans.modules.csl.spi.ParserResult; 33 | import org.openide.filesystems.FileObject; 34 | 35 | /** 36 | * 37 | * @author Christian Lenz 38 | */ 39 | /** 40 | * HTTPLangStructureScanner scans the parsed HTTP file (HTTPLangParserResult) 41 | * and produces structure items for each request line. 42 | * 43 | *

44 | * These structure items are instances of HTTPLangStructureItem and will be 45 | * displayed in the Navigator (Outline) of the editor. Each item represents a 46 | * request line in the HTTP file.

47 | */ 48 | public class HTTPLangStructureScanner implements StructureScanner { 49 | 50 | /** 51 | * Scans the parser result and returns a list of structure items. 52 | * 53 | * @param result the ParserResult (expected to be a HTTPLangParserResult) 54 | * @return a list of structure items, or an empty list if none found 55 | */ 56 | @Override 57 | public List scan(ParserResult result) { 58 | // Überprüfen, ob das ParserResult vom erwarteten Typ ist 59 | if (!(result instanceof HTTPLangParserResult)) { 60 | return Collections.emptyList(); 61 | } 62 | HTTPLangParserResult httpResult = (HTTPLangParserResult) result; 63 | 64 | // Stelle sicher, dass der Parser bereits ausgeführt wurde 65 | httpResult.parse(); 66 | 67 | List items = new ArrayList<>(); 68 | // Hole das FileObject aus dem Snapshot des ParserResults 69 | FileObject fo = httpResult.getSnapshot().getSource().getFileObject(); 70 | 71 | // Hole alle RequestLine-Kontexte, die im ParserResult gefunden wurden 72 | List requestLines = httpResult.getRequestLineContexts(); 73 | if (requestLines != null) { 74 | for (HTTPParser.RequestLineContext reqLine : requestLines) { 75 | // Nutze den Text der RequestLine als Display-Namen. Kürze ihn, falls er zu lang ist. 76 | String displayName = reqLine.getText(); 77 | if (displayName.length() > 40) { 78 | displayName = displayName.substring(0, 40) + "..."; 79 | } 80 | // Erstelle ein StructureItem für diese RequestLine. 81 | items.add(new HTTPLangStructureItem(fo, displayName, reqLine)); 82 | } 83 | } 84 | return items; 85 | } 86 | 87 | @Override 88 | public Map> folds(ParserResult pr) { 89 | return Collections.emptyMap(); 90 | } 91 | 92 | @Override 93 | public Configuration getConfiguration() { 94 | return new Configuration(true, false); 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/main/java/com/javierllorente/netbeans/rest/client/ui/LineNumberComponent.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 Christian Lenz . 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.javierllorente.netbeans.rest.client.ui; 17 | 18 | import java.awt.*; 19 | import java.awt.geom.Rectangle2D; 20 | import javax.swing.*; 21 | import javax.swing.text.*; 22 | 23 | /** 24 | * 25 | * @author Christian Lenz 26 | */ 27 | public class LineNumberComponent extends JComponent { 28 | 29 | private final JTextComponent textComponent; 30 | private int lastDigits = 0; 31 | 32 | public LineNumberComponent(JTextComponent textComponent) { 33 | this.textComponent = textComponent; 34 | Font font = textComponent.getFont(); 35 | setFont(font); 36 | setPreferredSize(new Dimension(40, textComponent.getHeight())); 37 | 38 | // adjustWidth(); 39 | } 40 | 41 | @Override 42 | protected void paintComponent(Graphics g) { 43 | super.paintComponent(g); 44 | 45 | Graphics2D g2d = (Graphics2D) g; 46 | 47 | g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); 48 | g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_LCD_HRGB); 49 | 50 | g2d.setColor(Color.GRAY); 51 | 52 | try { 53 | JViewport viewport = (JViewport) SwingUtilities.getAncestorOfClass(JViewport.class, textComponent); 54 | if (viewport != null) { 55 | Point viewPosition = viewport.getViewPosition(); 56 | Rectangle clip = g2d.getClipBounds(); 57 | 58 | int startOffset = textComponent.viewToModel2D(new Point(0, clip.y + viewPosition.y)); 59 | int endOffset = textComponent.viewToModel2D(new Point(0, clip.y + clip.height + viewPosition.y)); 60 | 61 | Document doc = textComponent.getDocument(); 62 | Element root = doc.getDefaultRootElement(); 63 | 64 | int startLine = root.getElementIndex(startOffset); 65 | int endLine = root.getElementIndex(endOffset); 66 | 67 | FontMetrics fontMetrics = g2d.getFontMetrics(); 68 | int fontAscent = fontMetrics.getAscent(); 69 | 70 | for (int line = startLine; line <= endLine; line++) { 71 | int lineStartOffset = root.getElement(line).getStartOffset(); 72 | Rectangle2D r = textComponent.modelToView2D(lineStartOffset); 73 | 74 | if (r != null && r.getY() - viewPosition.y + r.getHeight() > clip.y) { 75 | String lineNumber = String.valueOf(line + 1); 76 | int y = (int) Math.round(r.getY() - viewPosition.y + fontAscent); 77 | g2d.drawString(lineNumber, getWidth() - fontMetrics.stringWidth(lineNumber) - 5, y); 78 | } 79 | } 80 | } 81 | } catch (BadLocationException e) { 82 | e.printStackTrace(); 83 | } 84 | } 85 | 86 | private void adjustWidth() { 87 | int lines = getLineCount(); 88 | int digits = Math.max(String.valueOf(lines).length(), 1); 89 | 90 | if (digits != lastDigits) { 91 | int width = getFontMetrics(getFont()).stringWidth("0") * digits + 16; 92 | setPreferredSize(new Dimension(width, getHeight())); 93 | lastDigits = digits; 94 | revalidate(); 95 | } 96 | } 97 | 98 | private int getLineCount() { 99 | return textComponent.getDocument().getDefaultRootElement().getElementCount(); 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /src/main/java/com/javierllorente/netbeans/rest/client/hyperlink/RestHyperlinkProvider.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2022 The Apache Software Foundation 3 | * Copyright 2023 Javier Llorente 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | package com.javierllorente.netbeans.rest.client.hyperlink; 18 | 19 | import java.awt.Toolkit; 20 | import java.net.MalformedURLException; 21 | import java.net.URL; 22 | import java.util.EnumSet; 23 | import java.util.Set; 24 | import javax.swing.text.BadLocationException; 25 | import javax.swing.text.Document; 26 | import org.netbeans.api.editor.document.LineDocumentUtils; 27 | import org.netbeans.api.editor.mimelookup.MimeRegistration; 28 | import org.netbeans.api.editor.mimelookup.MimeRegistrations; 29 | import org.netbeans.editor.BaseDocument; 30 | import org.netbeans.lib.editor.hyperlink.spi.HyperlinkProviderExt; 31 | import org.netbeans.lib.editor.hyperlink.spi.HyperlinkType; 32 | import org.netbeans.lib.editor.util.swing.DocumentUtilities; 33 | import org.openide.util.Exceptions; 34 | import org.openide.util.Lookup; 35 | import com.javierllorente.netbeans.rest.client.parsers.Parser; 36 | import com.javierllorente.netbeans.rest.client.editor.RestMediaType; 37 | 38 | /** 39 | * 40 | * @author Jan Lahoda 41 | * @author Javier Llorente 42 | */ 43 | @MimeRegistrations({ 44 | @MimeRegistration(mimeType = RestMediaType.JSON, service = HyperlinkProviderExt.class), 45 | @MimeRegistration(mimeType = RestMediaType.XML, service = HyperlinkProviderExt.class) 46 | }) 47 | public class RestHyperlinkProvider implements HyperlinkProviderExt { 48 | 49 | @Override 50 | public Set getSupportedHyperlinkTypes() { 51 | return EnumSet.of(HyperlinkType.GO_TO_DECLARATION); 52 | } 53 | 54 | @Override 55 | public boolean isHyperlinkPoint(Document doc, int offset, HyperlinkType type) { 56 | return getHyperlinkSpan(doc, offset, type) != null; 57 | } 58 | 59 | @Override 60 | public int[] getHyperlinkSpan(Document doc, int offset, HyperlinkType type) { 61 | if (!(doc instanceof BaseDocument)) { 62 | return null; 63 | } 64 | 65 | try { 66 | BaseDocument bdoc = (BaseDocument) doc; 67 | int start = LineDocumentUtils.getLineStart(bdoc, offset); 68 | int end = LineDocumentUtils.getLineEnd(bdoc, offset); 69 | 70 | for (int[] span : Parser.recognizeURLs(DocumentUtilities.getText(doc, start, end - start))) { 71 | if (span[0] + start <= offset && offset <= span[1] + start) { 72 | return new int[]{ 73 | span[0] + start, 74 | span[1] + start 75 | }; 76 | } 77 | } 78 | } catch (BadLocationException ex) { 79 | Exceptions.printStackTrace(ex); 80 | } 81 | 82 | return null; 83 | } 84 | 85 | @Override 86 | public void performClickAction(Document doc, int offset, HyperlinkType type) { 87 | int[] span = getHyperlinkSpan(doc, offset, type); 88 | 89 | if (span == null) { 90 | Toolkit.getDefaultToolkit().beep(); 91 | return; 92 | } 93 | 94 | RestURLDisplayer urlDisplayer = Lookup.getDefault().lookup(RestURLDisplayer.class); 95 | 96 | try { 97 | String urlText = doc.getText(span[0], span[1] - span[0]); 98 | urlDisplayer.showURL(new URL(urlText)); 99 | } catch (BadLocationException | MalformedURLException ex) { 100 | Exceptions.printStackTrace(ex); 101 | } 102 | } 103 | 104 | @Override 105 | public String getTooltipText(Document doc, int offset, HyperlinkType type) { 106 | return null; 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /src/main/java/com/javierllorente/netbeans/rest/util/FormatUtils.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022-2024 Javier Llorente . 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.javierllorente.netbeans.rest.util; 17 | 18 | import jakarta.json.Json; 19 | import jakarta.json.JsonArray; 20 | import jakarta.json.JsonObject; 21 | import jakarta.json.JsonStructure; 22 | import jakarta.json.JsonWriter; 23 | import jakarta.json.JsonWriterFactory; 24 | import jakarta.json.stream.JsonGenerator; 25 | import java.io.IOException; 26 | import java.io.InputStream; 27 | import java.io.StringReader; 28 | import java.io.StringWriter; 29 | import java.nio.charset.StandardCharsets; 30 | import java.util.HashMap; 31 | import java.util.Map; 32 | import javax.xml.XMLConstants; 33 | import javax.xml.transform.OutputKeys; 34 | import javax.xml.transform.Source; 35 | import javax.xml.transform.Transformer; 36 | import javax.xml.transform.TransformerException; 37 | import javax.xml.transform.TransformerFactory; 38 | import javax.xml.transform.stream.StreamResult; 39 | import javax.xml.transform.stream.StreamSource; 40 | import org.jsoup.Jsoup; 41 | import org.jsoup.nodes.Document; 42 | import org.openide.util.Exceptions; 43 | 44 | /** 45 | * 46 | * @author Javier Llorente 47 | */ 48 | public class FormatUtils { 49 | 50 | private FormatUtils() { 51 | } 52 | 53 | public static String jsonPrettyFormat(JsonStructure jsonStructure) { 54 | Map map = new HashMap<>(); 55 | map.put(JsonGenerator.PRETTY_PRINTING, true); 56 | JsonWriterFactory writerFactory = Json.createWriterFactory(map); 57 | StringWriter stringWriter = new StringWriter(); 58 | try (final JsonWriter jsonWriter = writerFactory.createWriter(stringWriter)) { 59 | if (jsonStructure instanceof JsonObject) { 60 | jsonWriter.writeObject((JsonObject) jsonStructure); 61 | } else { 62 | jsonWriter.writeArray((JsonArray) jsonStructure); 63 | } 64 | } 65 | return stringWriter.toString(); 66 | } 67 | 68 | public static String xmlPrettyFormat(String input) { 69 | Source xmlInput = new StreamSource(new StringReader(input)); 70 | StringWriter stringWriter = new StringWriter(); 71 | StreamResult xmlOutput = new StreamResult(stringWriter); 72 | TransformerFactory transformerFactory = TransformerFactory.newInstance(); 73 | transformerFactory.setAttribute("indent-number", 2); 74 | transformerFactory.setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, ""); 75 | transformerFactory.setAttribute(XMLConstants.ACCESS_EXTERNAL_STYLESHEET, ""); 76 | try { 77 | Transformer transformer = transformerFactory.newTransformer(readXsl()); 78 | transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes"); 79 | transformer.setOutputProperty(OutputKeys.INDENT, "yes"); 80 | transformer.transform(xmlInput, xmlOutput); 81 | } catch (TransformerException ex) { 82 | Exceptions.printStackTrace(ex); 83 | } 84 | return xmlOutput.getWriter().toString(); 85 | } 86 | 87 | private static Source readXsl() { 88 | InputStream inputStream = FormatUtils.class.getResourceAsStream("prettyprint.xsl"); 89 | Source xslSource = null; 90 | try { 91 | if (inputStream != null) { 92 | String xsl = new String(inputStream.readAllBytes(), StandardCharsets.UTF_8); 93 | inputStream.close(); 94 | xslSource = new StreamSource(new StringReader(xsl)); 95 | } 96 | } catch (IOException ex) { 97 | Exceptions.printStackTrace(ex); 98 | } 99 | return xslSource; 100 | } 101 | 102 | public static String htmlPrettyFormat(String input) { 103 | Document doc = Jsoup.parse(input); 104 | return doc.toString(); 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /src/main/java/com/javierllorente/netbeans/rest/client/http/editor/sidebar/ResponseSidebarManager.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 Christian Lenz . 3 | * 4 | * Licensed to the Apache Software Foundation (ASF) under one 5 | * or more contributor license agreements. See the NOTICE file 6 | * distributed with this work for additional information 7 | * regarding copyright ownership. The ASF licenses this file 8 | * to you under the Apache License, Version 2.0 (the 9 | * "License"); you may not use this file except in compliance 10 | * with the License. You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, 15 | * software distributed under the License is distributed on an 16 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 | * KIND, either express or implied. See the License for the 18 | * specific language governing permissions and limitations 19 | * under the License. 20 | */ 21 | package com.javierllorente.netbeans.rest.client.http.editor.sidebar; 22 | 23 | import jakarta.ws.rs.core.MultivaluedMap; 24 | import java.util.WeakHashMap; 25 | import javax.swing.SwingUtilities; 26 | import javax.swing.text.JTextComponent; 27 | 28 | /** 29 | * Manager for controlling the response sidebar programmatically. 30 | * This singleton allows RequestProcessor and other classes to show responses 31 | * in the sidebar. 32 | * 33 | * @author Christian Lenz 34 | */ 35 | public class ResponseSidebarManager { 36 | 37 | private static final ResponseSidebarManager INSTANCE = new ResponseSidebarManager(); 38 | private final WeakHashMap sidebars; 39 | 40 | private ResponseSidebarManager() { 41 | this.sidebars = new WeakHashMap<>(); 42 | } 43 | 44 | public static ResponseSidebarManager getInstance() { 45 | return INSTANCE; 46 | } 47 | 48 | /** 49 | * Register a sidebar panel for a specific text component. 50 | * Called by ResponseSideBarFactory when creating sidebars. 51 | */ 52 | public void registerSidebar(JTextComponent textComponent, ResponseSidebarPanel panel) { 53 | sidebars.put(textComponent, panel); 54 | } 55 | 56 | /** 57 | * Show a response in the sidebar for the given text component. 58 | * 59 | * @param textComponent The editor component 60 | * @param response The response content 61 | * @param contentType The content type (e.g., "application/json") 62 | */ 63 | public void showResponse(JTextComponent textComponent, String response, String contentType) { 64 | showResponse(textComponent, response, contentType, null); 65 | } 66 | 67 | /** 68 | * Show a response in the sidebar for the given text component with headers. 69 | * 70 | * @param textComponent The editor component 71 | * @param response The response content 72 | * @param contentType The content type (e.g., "application/json") 73 | * @param headers The response headers 74 | */ 75 | public void showResponse(JTextComponent textComponent, String response, String contentType, MultivaluedMap headers) { 76 | if (textComponent == null) { 77 | return; 78 | } 79 | 80 | // Ensure UI updates happen on the Event Dispatch Thread 81 | SwingUtilities.invokeLater(() -> { 82 | ResponseSidebarPanel panel = sidebars.get(textComponent); 83 | if (panel != null) { 84 | try { 85 | panel.showResponse(response, contentType, headers); 86 | } catch (Exception e) { 87 | // Log but don't propagate exceptions 88 | System.err.println("Error showing response in sidebar: " + e.getMessage()); 89 | e.printStackTrace(); 90 | } 91 | } 92 | }); 93 | } 94 | 95 | /** 96 | * Hide the sidebar for the given text component. 97 | */ 98 | public void hideSidebar(JTextComponent textComponent) { 99 | if (textComponent == null) { 100 | return; 101 | } 102 | 103 | SwingUtilities.invokeLater(() -> { 104 | ResponseSidebarPanel panel = sidebars.get(textComponent); 105 | if (panel != null) { 106 | try { 107 | panel.setVisible(false); 108 | } catch (Exception e) { 109 | System.err.println("Error hiding sidebar: " + e.getMessage()); 110 | e.printStackTrace(); 111 | } 112 | } 113 | }); 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /src/main/java/com/javierllorente/netbeans/rest/client/parsers/UrlParamsParser.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 Javier Llorente . 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.javierllorente.netbeans.rest.client.parsers; 17 | 18 | import com.javierllorente.netbeans.rest.client.ui.TablePanel; 19 | import java.util.Arrays; 20 | import java.util.List; 21 | import java.util.logging.Level; 22 | import java.util.logging.Logger; 23 | 24 | /** 25 | * 26 | * @author Javier Llorente 27 | */ 28 | public class UrlParamsParser { 29 | 30 | private static final Logger logger = Logger.getLogger(UrlParamsParser.class.getName()); 31 | private final TablePanel paramsPanel; 32 | 33 | public UrlParamsParser(TablePanel paramsPanel) { 34 | this.paramsPanel = paramsPanel; 35 | } 36 | 37 | public void processChanges(String urlFieldUpdate) { 38 | logger.log(Level.INFO, "processChanges() urlFieldUpdate = {0}", urlFieldUpdate); 39 | List url = Arrays.asList(urlFieldUpdate.split("\\?", 2)); 40 | 41 | if (url.size() > 1) { 42 | String urlFieldParams = url.get(1); 43 | List params = Arrays.asList(urlFieldParams.split("\\&", -1)); 44 | 45 | if (params.size() >= paramsPanel.getRowCount()) { 46 | // Add/edit rows 47 | 48 | for (int i = 0; i < params.size(); i++) { 49 | List entry = Arrays.asList(params.get(i).split("=", 2)); 50 | 51 | if (params.size() > paramsPanel.getRowCount()) { 52 | paramsPanel.addRow(entry.get(0), entry.size() > 1 ? entry.get(1) : ""); 53 | paramsPanel.selectLastItem(); 54 | paramsPanel.showLastItem(); 55 | } 56 | 57 | if (!paramsPanel.getKey(i).equals(entry.get(0)) 58 | || !paramsPanel.getValue(i).equals(entry.size() > 1 ? entry.get(1) : "")) { 59 | paramsPanel.editRow(i, entry.get(0), 60 | entry.size() > 1 ? entry.get(1) : ""); 61 | paramsPanel.changeSelection(i, 1); 62 | logger.log(Level.INFO, "changeSelection to row: {0}", i); 63 | } 64 | } 65 | 66 | } else if (params.size() < paramsPanel.getRowCount()) { 67 | // Remove rows 68 | 69 | for (int i = 0; i < paramsPanel.getRowCount(); i++) { 70 | String row = paramsPanel.getKey(i) + "=" + paramsPanel.getValue(i); 71 | if (row.equals("=")) { 72 | row = ""; 73 | } 74 | 75 | if (i <= params.size() - 1 && !params.get(i).equals(row)) { 76 | paramsPanel.removeRow(i); 77 | paramsPanel.changeSelection(i, 1); 78 | 79 | if (i <= params.size() - 1 && !params.get(i).equals(row)) { 80 | List entry = Arrays.asList(params.get(i).split("=", 2)); 81 | paramsPanel.editRow(i, entry.get(0), 82 | entry.size() > 1 ? entry.get(1) : ""); 83 | paramsPanel.changeSelection(i, 1); 84 | logger.log(Level.INFO, "changeSelection to row: {0}", i); 85 | } 86 | 87 | } else if (i > params.size() - 1 88 | && params.size() < paramsPanel.getRowCount()) { 89 | paramsPanel.removeRow(i); 90 | paramsPanel.changeSelection(--i, 1); 91 | } 92 | } 93 | 94 | } 95 | 96 | 97 | } else if (!paramsPanel.isEmpty()) { 98 | logger.info("removeAllRows!"); 99 | paramsPanel.removeAllRows(); 100 | } 101 | 102 | } 103 | 104 | } 105 | -------------------------------------------------------------------------------- /src/main/antlr4/HTTPLexer.g4: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 Christian Lenz . 3 | * 4 | * Licensed to the Apache Software Foundation (ASF) under one 5 | * or more contributor license agreements. See the NOTICE file 6 | * distributed with this work for additional information 7 | * regarding copyright ownership. The ASF licenses this file 8 | * to you under the Apache License, Version 2.0 (the 9 | * "License"); you may not use this file except in compliance 10 | * with the License. You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, 15 | * software distributed under the License is distributed on an 16 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 | * KIND, either express or implied. See the License for the 18 | * specific language governing permissions and limitations 19 | * under the License. 20 | */ 21 | 22 | /** 23 | * 24 | * @author Christian Lenz 25 | */ 26 | lexer grammar HTTPLexer; 27 | 28 | tokens { OPEN_BLOCK_BRAKET, CLOSE_BLOCK_BRAKET } 29 | 30 | WS: [ \t]; 31 | 32 | // For JSON and text body - only consume newlines, not body content 33 | BODY_START_WITH_BLANK: ('\r'? '\n') WS* {_input.LA(1) == '{'}? -> pushMode(BODY_CONTENT); 34 | GENERIC_BODY_START: ('\r'? '\n') ('\r'? '\n') {_input.LA(1) >= 'a' && _input.LA(1) <= 'z' || _input.LA(1) == '<'}? -> pushMode(BODY_CONTENT); 35 | 36 | NEWLINE: ('\r'? '\n'); 37 | REQUEST_SEPARATOR : '###' (~[\r\n])*; 38 | COMMENT: ('#' | '//') (~[\r\n])*; 39 | 40 | fragment OPTIONAL_WS: WS*; 41 | fragment REQUIRED_WS: WS+; 42 | 43 | METHOD 44 | : 'GET' 45 | | 'HEAD' 46 | | 'POST' 47 | | 'PUT' 48 | | 'DELETE' 49 | | 'CONNECT' 50 | | 'PATCH' 51 | | 'OPTIONS' 52 | | 'TRACE' 53 | ; 54 | 55 | HTTP_PROTOCOL : 'HTTP'; 56 | 57 | COLON: ':'; 58 | DOT: '.'; 59 | COMMA: ','; 60 | SLASH: '/'; 61 | QUESTION_MARK: '?'; 62 | HASH: '#'; 63 | ASTERISK: '*'; 64 | EQUAL: '='; 65 | AMPERSAND: '&'; 66 | PERCENT: '%'; 67 | OPEN_BRAKET: '['; 68 | CLOSE_BRAKET: ']'; 69 | QUOTE: '"'; 70 | 71 | fragment DIGIT: [0-9]; 72 | DIGITS: DIGIT+; 73 | 74 | SCHEME 75 | : 'http' 76 | | 'https' 77 | | 'HTTPS' 78 | | 'ws' 79 | | 'WS' 80 | | 'wss' 81 | | 'WSS' 82 | | 'ftp' 83 | | 'FTP' 84 | | 'ftps' 85 | | 'FTPS' 86 | | 'sftp' 87 | | 'SFTP' 88 | | 'ldap' 89 | | 'LDAP' 90 | | 'ldaps' 91 | | 'LDAPS' 92 | | 'imap' 93 | | 'IMAP' 94 | | 'imaps' 95 | | 'IMAPS' 96 | | 'smtp' 97 | | 'SMTP' 98 | | 'smtps' 99 | | 'SMTPS' 100 | | 'pop3' 101 | | 'POP3' 102 | | 'pop3s' 103 | | 'POP3S' 104 | | 'file' 105 | | 'FILE' 106 | | 'telnet' 107 | | 'TELNET' 108 | | 'rtsp' 109 | | 'RTSP' 110 | | 'smb' 111 | | 'SMB' 112 | | 'nfs' 113 | | 'NFS' 114 | | 'git' 115 | | 'GIT' 116 | | 'mms' 117 | | 'MMS' 118 | | 'news' 119 | | 'NEWS' 120 | | 'urn' 121 | | 'URN' 122 | | 'data' 123 | | 'DATA' 124 | ; 125 | 126 | fragment LINE_TAIL: (~[\r\n])* NEWLINE; 127 | 128 | ALPHA_CHARS: [a-zA-Z]+; 129 | 130 | DASH: '-'; 131 | 132 | UNDERSCORE: '_'; 133 | 134 | SCHEME_SEPARATOR : '://'; 135 | 136 | INPUT_FILE_REF : '<' REQUIRED_WS LINE_TAIL; 137 | 138 | RESPONSE_HANDLER_SCRIPT_START : '>' REQUIRED_WS '{%'; 139 | RESPONSE_HANDLER_SCRIPT_END : '%}'; 140 | RESPONSE_HANDLER_FILE_REF : '>' REQUIRED_WS LINE_TAIL; 141 | 142 | SCRIPT_CONTENT : . ; 143 | 144 | // Environment variables 145 | ENV_VARIABLE : '{{' OPTIONAL_WS (ALPHA_CHARS DIGITS)* OPTIONAL_WS '}}'; 146 | 147 | // Multipart-Form-Data 148 | MULTIPART_BOUNDARY : '--' (~[\r\n])* '-' '--'; 149 | MULTIPART_PART : '--' (~[\r\n])* '-'; 150 | 151 | // Error case: Body without blank line - keep consuming '{' 152 | BODY_START_NO_BLANK: '{' -> pushMode(BODY_CONTENT); 153 | 154 | // Ignore everything else 155 | ERROR : .; 156 | 157 | // BODY_CONTENT mode: Accepts JSON, text, HTML, XML, CSV, etc. 158 | mode BODY_CONTENT; 159 | 160 | JSON_LBRACE : '{' -> type(OPEN_BLOCK_BRAKET) ; 161 | JSON_RBRACE : '}' -> type(CLOSE_BLOCK_BRAKET), popMode ; 162 | JSON_LBRACK : '[' -> type(OPEN_BRAKET) ; 163 | JSON_RBRACK : ']' -> type(CLOSE_BRAKET) ; 164 | JSON_COLON : ':' -> type(COLON) ; 165 | JSON_COMMA : ',' -> type(COMMA) ; 166 | 167 | STRING 168 | : '"' ( '\\' . | ~["\\\r\n] )* '"' 169 | ; 170 | 171 | NUMBER 172 | : '-'? DIGIT+ ('.' DIGIT+)? ([eE][+-]? DIGIT+)? 173 | ; 174 | 175 | TRUE : 'true' ; 176 | FALSE : 'false'; 177 | NULL : 'null' ; 178 | 179 | JSON_NEWLINE_SEPARATOR : ('\r'? '\n') WS* '###' (~[\r\n])* -> type(REQUEST_SEPARATOR), popMode; 180 | JSON_NEWLINE : ('\r'? '\n') -> type(NEWLINE); 181 | JSON_WS : [ \t]+ -> type(WS); 182 | 183 | // Generic body content for non-JSON bodies (text, HTML, XML, CSV, etc.) 184 | BODY_TEXT : ~[\r\n]+ ; 185 | 186 | JSON_EOF_CLEANUP 187 | : EOF -> popMode, type(EOF) 188 | ; 189 | -------------------------------------------------------------------------------- /src/main/java/com/javierllorente/netbeans/rest/client/ui/RestClientOptionsPanel.form: -------------------------------------------------------------------------------- 1 | 2 | 3 | 23 | 24 |
25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 |
106 | -------------------------------------------------------------------------------- /src/main/java/com/javierllorente/netbeans/rest/client/ui/UrlPanel.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 Javier Llorente . 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.javierllorente.netbeans.rest.client.ui; 17 | 18 | import java.awt.event.ActionListener; 19 | import java.awt.event.FocusListener; 20 | import java.awt.event.KeyListener; 21 | import javax.swing.SwingUtilities; 22 | import javax.swing.event.DocumentListener; 23 | 24 | /** 25 | * 26 | * @author Javier Llorente 27 | */ 28 | public class UrlPanel extends javax.swing.JPanel { 29 | 30 | /** 31 | * Creates new form UrlPanel 32 | */ 33 | public UrlPanel() { 34 | initComponents(); 35 | } 36 | 37 | public String getUrl() { 38 | return urlTextField.getText(); 39 | } 40 | 41 | public void setUrl(String url) { 42 | urlTextField.setText(url); 43 | } 44 | 45 | public void moveCaretToEnd() { 46 | urlTextField.setCaretPosition(urlTextField.getText().length()); 47 | } 48 | 49 | public String getDisplayUrl() { 50 | String url = urlTextField.getText(); 51 | if (url.length() > 19) { 52 | url = url.substring(0, 20) + "..."; 53 | } 54 | return url; 55 | } 56 | 57 | public void addUrlDocumentListener(DocumentListener dl) { 58 | urlTextField.getDocument().addDocumentListener(dl); 59 | } 60 | 61 | public void removeUrlDocumentListener(DocumentListener dl) { 62 | urlTextField.getDocument().removeDocumentListener(dl); 63 | } 64 | 65 | public void addUrlFocusListener(FocusListener fl) { 66 | urlTextField.addFocusListener(fl); 67 | } 68 | 69 | public void requestUrlFocus() { 70 | SwingUtilities.invokeLater(() -> { 71 | urlTextField.requestFocus(); 72 | }); 73 | } 74 | 75 | public String getRequestMethod() { 76 | return methodComboBox.getSelectedItem().toString(); 77 | } 78 | 79 | public void setRequestMethod(String method) { 80 | methodComboBox.setSelectedItem(method); 81 | } 82 | 83 | public void addSendButtonActionListener(ActionListener al) { 84 | sendButton.addActionListener(al); 85 | } 86 | 87 | public void addComboBoxActionListener(ActionListener al) { 88 | methodComboBox.addActionListener(al); 89 | } 90 | 91 | public void addUrlKeyListener(KeyListener kl) { 92 | urlTextField.addKeyListener(kl); 93 | } 94 | 95 | /** 96 | * This method is called from within the constructor to initialize the form. 97 | * WARNING: Do NOT modify this code. The content of this method is always 98 | * regenerated by the Form Editor. 99 | */ 100 | @SuppressWarnings("unchecked") 101 | // //GEN-BEGIN:initComponents 102 | private void initComponents() { 103 | 104 | methodComboBox = new javax.swing.JComboBox<>(); 105 | urlTextField = new javax.swing.JTextField(); 106 | urlTextField.putClientProperty("JTextField.selectAllOnFocusPolicy", "never"); 107 | sendButton = new javax.swing.JButton(); 108 | 109 | methodComboBox.setModel(new javax.swing.DefaultComboBoxModel<>(new String[] { "GET", "POST", "PUT", "PATCH", "DELETE" })); 110 | 111 | org.openide.awt.Mnemonics.setLocalizedText(sendButton, "Send"); 112 | 113 | javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); 114 | this.setLayout(layout); 115 | layout.setHorizontalGroup( 116 | layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) 117 | .addGroup(layout.createSequentialGroup() 118 | .addContainerGap() 119 | .addComponent(methodComboBox, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) 120 | .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) 121 | .addComponent(urlTextField, javax.swing.GroupLayout.DEFAULT_SIZE, 632, Short.MAX_VALUE) 122 | .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) 123 | .addComponent(sendButton) 124 | .addContainerGap()) 125 | ); 126 | layout.setVerticalGroup( 127 | layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) 128 | .addGroup(layout.createSequentialGroup() 129 | .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) 130 | .addComponent(methodComboBox, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) 131 | .addComponent(urlTextField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) 132 | .addComponent(sendButton)) 133 | .addGap(0, 0, Short.MAX_VALUE)) 134 | ); 135 | }// //GEN-END:initComponents 136 | 137 | 138 | // Variables declaration - do not modify//GEN-BEGIN:variables 139 | private javax.swing.JComboBox methodComboBox; 140 | private javax.swing.JButton sendButton; 141 | private javax.swing.JTextField urlTextField; 142 | // End of variables declaration//GEN-END:variables 143 | 144 | } 145 | -------------------------------------------------------------------------------- /src/main/java/com/javierllorente/netbeans/rest/client/http/editor/navigator/HTTPLangStructureItem.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 Christian Lenz . 3 | * 4 | * Licensed to the Apache Software Foundation (ASF) under one 5 | * or more contributor license agreements. See the NOTICE file 6 | * distributed with this work for additional information 7 | * regarding copyright ownership. The ASF licenses this file 8 | * to you under the Apache License, Version 2.0 (the 9 | * "License"); you may not use this file except in compliance 10 | * with the License. You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, 15 | * software distributed under the License is distributed on an 16 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 | * KIND, either express or implied. See the License for the 18 | * specific language governing permissions and limitations 19 | * under the License. 20 | */ 21 | package com.javierllorente.netbeans.rest.client.http.editor.navigator; 22 | 23 | import com.javierllorente.netbeans.rest.client.http.editor.syntax.coloring.HTTPTokenId; 24 | import java.util.Collections; 25 | import java.util.List; 26 | import java.util.Set; 27 | import javax.swing.ImageIcon; 28 | import org.antlr.v4.runtime.ParserRuleContext; 29 | import org.netbeans.modules.csl.api.ElementHandle; 30 | import org.netbeans.modules.csl.api.ElementKind; 31 | import org.netbeans.modules.csl.api.HtmlFormatter; 32 | import org.netbeans.modules.csl.api.Modifier; 33 | import org.netbeans.modules.csl.api.OffsetRange; 34 | import org.netbeans.modules.csl.api.StructureItem; 35 | import org.netbeans.modules.csl.spi.ParserResult; 36 | import org.openide.filesystems.FileObject; 37 | import org.openide.util.ImageUtilities; 38 | 39 | /** 40 | * 41 | * @author Christian Lenz 42 | */ 43 | /** 44 | * HTTPLangStructureItem represents a single request line in an HTTP file.\n It 45 | * is used by the StructureScanner to display items in the Navigator.\n 46 | */ 47 | public class HTTPLangStructureItem implements StructureItem, ElementHandle { 48 | 49 | private final String name; 50 | private final int startPosition; 51 | private final int stopPosition; 52 | private final FileObject fo; 53 | 54 | /** 55 | * Constructs a new structure item with the given name and offset range. 56 | * 57 | * @param name The display name for this structure item.\n 58 | */ 59 | // Im Konstruktor: 60 | public HTTPLangStructureItem(FileObject fo, String name, ParserRuleContext context) { 61 | this.name = name; 62 | // Add null checks for context and its start/stop tokens 63 | this.startPosition = (context != null && context.start != null) ? context.start.getStartIndex() : 0; // Default to 0 if null 64 | this.stopPosition = (context != null && context.stop != null) ? context.stop.getStopIndex() + 1 : this.startPosition; // Default to start if stop is null 65 | this.fo = fo; 66 | 67 | // Log warning if context or tokens were null 68 | if (context == null || context.start == null || context.stop == null) { 69 | // Use your logger if available, otherwise System.out 70 | System.err.println("Warning: HTTPLangStructureItem created with null context or tokens for name: " + name); 71 | } 72 | } 73 | 74 | /** 75 | * Returns the display name of the structure item. 76 | * 77 | * @return The name. 78 | */ 79 | @Override 80 | public String getName() { 81 | return name; 82 | } 83 | 84 | /** 85 | * Returns the kind of element. Hier kann man beispielsweise REQUEST als\n 86 | * eigenen Typ definieren, hier nutzen wir OTHER als Platzhalter. 87 | * 88 | * @return The element kind. 89 | */ 90 | @Override 91 | public ElementKind getKind() { 92 | return ElementKind.OTHER; 93 | } 94 | 95 | /** 96 | * Returns the text used for sorting structure items. 97 | * Returns the position padded with zeros to ensure items are sorted 98 | * in the order they appear in the file (top to bottom), not alphabetically. 99 | * 100 | * @return The sort text based on position. 101 | */ 102 | @Override 103 | public String getSortText() { 104 | // Use position with leading zeros for correct lexicographic sorting 105 | // Max file size: 10 million characters = 8 digits 106 | return String.format("%08d", startPosition); 107 | } 108 | 109 | /** 110 | * Returns the HTML representation of the structure item, used in the 111 | * Navigator.\n Hier wird der Name in einfachem Text dargestellt. 112 | * 113 | * @param formatter The HTML formatter. 114 | * @return The HTML text. 115 | */ 116 | @Override 117 | public String getHtml(HtmlFormatter formatter) { 118 | formatter.appendText(name); 119 | return formatter.getText(); 120 | } 121 | 122 | /** 123 | * Returns any nested structure items. Da wir nur RequestLines anzeigen,\n 124 | * gibt es keine Verschachtelung. 125 | * 126 | * @return An empty list. 127 | */ 128 | @Override 129 | public List getNestedItems() { 130 | return Collections.emptyList(); 131 | } 132 | 133 | @Override 134 | public ElementHandle getElementHandle() { 135 | return this; 136 | } 137 | 138 | @Override 139 | public Set getModifiers() { 140 | return Collections.emptySet(); 141 | } 142 | 143 | @Override 144 | public boolean isLeaf() { 145 | return true; 146 | } 147 | 148 | @Override 149 | public long getPosition() { 150 | return startPosition; 151 | } 152 | 153 | @Override 154 | public long getEndPosition() { 155 | return stopPosition; 156 | } 157 | 158 | @Override 159 | public ImageIcon getCustomIcon() { 160 | return new ImageIcon(ImageUtilities.loadImage("com/javierllorente/netbeans/rest/client/http/editor/http.png")); 161 | } 162 | 163 | @Override 164 | public FileObject getFileObject() { 165 | return fo; 166 | } 167 | 168 | @Override 169 | public String getMimeType() { 170 | return HTTPTokenId.MIME_TYPE; 171 | } 172 | 173 | @Override 174 | public String getIn() { 175 | return null; 176 | } 177 | 178 | @Override 179 | public boolean signatureEquals(ElementHandle eh) { 180 | return false; 181 | } 182 | 183 | @Override 184 | public OffsetRange getOffsetRange(ParserResult pr) { 185 | return new OffsetRange(startPosition, stopPosition); 186 | } 187 | } 188 | -------------------------------------------------------------------------------- /src/main/java/com/javierllorente/netbeans/rest/client/parsers/PostmanUtilities.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 Javier Llorente . 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.javierllorente.netbeans.rest.client.parsers; 17 | 18 | import com.javierllorente.netbeans.rest.util.FormatUtils; 19 | import com.javierllorente.netbeans.rest.client.ui.RestClientTopComponent; 20 | import jakarta.json.Json; 21 | import jakarta.json.JsonArray; 22 | import jakarta.json.JsonArrayBuilder; 23 | import jakarta.json.JsonObject; 24 | import jakarta.json.JsonReader; 25 | import jakarta.json.JsonValue; 26 | import jakarta.ws.rs.core.MultivaluedHashMap; 27 | import jakarta.ws.rs.core.MultivaluedMap; 28 | import java.io.IOException; 29 | import java.io.StringReader; 30 | import java.nio.file.Files; 31 | import java.nio.file.Path; 32 | import java.util.List; 33 | import java.util.Map; 34 | import javax.swing.SwingUtilities; 35 | import org.openide.windows.TopComponent; 36 | 37 | /** 38 | * 39 | * @author Javier Llorente 40 | */ 41 | public class PostmanUtilities { 42 | 43 | private static final String SCHEMA = "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"; 44 | 45 | private PostmanUtilities() { 46 | } 47 | 48 | public static int importRequests(Path path) throws IOException { 49 | int imported = 0; 50 | String json = Files.readString(path); 51 | 52 | try (JsonReader reader = Json.createReader(new StringReader(json))) { 53 | 54 | JsonObject collection = reader.readObject(); 55 | JsonArray itemArray = collection.getJsonArray("item"); 56 | 57 | if (itemArray != null) { 58 | for (JsonValue jsonValue : itemArray) { 59 | JsonObject itemObject = jsonValue.asJsonObject(); 60 | JsonObject requestObject = itemObject.getJsonObject("request"); 61 | if (requestObject != null) { 62 | JsonArray headerArray = requestObject.getJsonArray("header"); 63 | MultivaluedMap headers = new MultivaluedHashMap<>(); 64 | 65 | if (headerArray != null && !headerArray.isEmpty()) { 66 | for (int i = 0; i < headerArray.size(); i++) { 67 | JsonObject header = headerArray.getJsonObject(i); 68 | headers.add(header.getString("key"), header.getString("value")); 69 | } 70 | } 71 | 72 | SwingUtilities.invokeLater(() -> { 73 | RestClientTopComponent component = new RestClientTopComponent(); 74 | component.open(); 75 | String url = null; 76 | JsonValue urlValue = requestObject.get("url"); 77 | switch (urlValue.getValueType()) { 78 | case STRING: 79 | url = requestObject.getString("url"); 80 | break; 81 | case OBJECT: 82 | JsonObject urlObject = requestObject.getJsonObject("url"); 83 | url = urlObject.getString("raw"); 84 | break; 85 | default: 86 | throw new AssertionError(urlValue.getValueType().name()); 87 | } 88 | component.setUrl(url); 89 | component.setRequestMethod(requestObject.getString("method")); 90 | // Remove User-Agent (automatically added on start-up) 91 | component.clearHeaders(); 92 | component.setHeaders(headers); 93 | component.requestActive(); 94 | }); 95 | 96 | ++imported; 97 | } 98 | } 99 | } 100 | } 101 | 102 | return imported; 103 | } 104 | 105 | public static int exportRequests(Path path) throws IOException { 106 | int exported = 0; 107 | JsonArrayBuilder items = Json.createArrayBuilder(); 108 | 109 | for (var topComponent : TopComponent.getRegistry().getOpened()) { 110 | if (topComponent instanceof RestClientTopComponent) { 111 | RestClientTopComponent restClientTopComponent = (RestClientTopComponent) topComponent; 112 | 113 | JsonArrayBuilder headers = Json.createArrayBuilder(); 114 | for (Map.Entry> entry : restClientTopComponent 115 | .getHeaders().entrySet()) { 116 | headers.add(Json.createObjectBuilder() 117 | .add("key", entry.getKey()) 118 | .add("value", entry.getValue().get(0)) 119 | .build()); 120 | } 121 | 122 | JsonObject item = Json.createObjectBuilder() 123 | .add("name", restClientTopComponent.getDisplayUrl()) 124 | .add("request", Json.createObjectBuilder() 125 | .add("method", restClientTopComponent.getRequestMethod()) 126 | .add("url", restClientTopComponent.getUrl()) 127 | .add("header", headers.build()) 128 | .build()) 129 | .build(); 130 | items.add(item); 131 | ++exported; 132 | } 133 | } 134 | 135 | JsonObject json = Json.createObjectBuilder() 136 | .add("info", Json.createObjectBuilder() 137 | .add("name", "NetBeans REST Client") 138 | .add("schema", SCHEMA) 139 | .build()) 140 | .add("item", items.build()) 141 | .build(); 142 | Files.writeString(path, FormatUtils.jsonPrettyFormat(json)); 143 | 144 | return exported; 145 | } 146 | 147 | } 148 | -------------------------------------------------------------------------------- /src/main/java/com/javierllorente/netbeans/rest/client/ui/BodyPanel.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 Javier Llorente . 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.javierllorente.netbeans.rest.client.ui; 17 | 18 | import java.awt.CardLayout; 19 | import javax.swing.text.StyledEditorKit; 20 | 21 | /** 22 | * 23 | * @author Javier Llorente 24 | */ 25 | public class BodyPanel extends javax.swing.JPanel { 26 | 27 | /** 28 | * Creates new form BodyPanel 29 | */ 30 | public BodyPanel() { 31 | initComponents(); 32 | } 33 | 34 | public String getBodyType() { 35 | return bodyComboBox.getSelectedItem().toString(); 36 | } 37 | 38 | public void setBodyType(String bodyType) { 39 | bodyComboBox.setSelectedItem(bodyType); 40 | } 41 | 42 | public String getBody() { 43 | return editorPane.getText(); 44 | } 45 | 46 | public void setBody(String body) { 47 | editorPane.setText(body); 48 | } 49 | 50 | public void setComboBoxEnabled(boolean enable) { 51 | bodyComboBox.setEnabled(enable); 52 | } 53 | 54 | /** 55 | * This method is called from within the constructor to initialize the form. 56 | * WARNING: Do NOT modify this code. The content of this method is always 57 | * regenerated by the Form Editor. 58 | */ 59 | @SuppressWarnings("unchecked") 60 | // //GEN-BEGIN:initComponents 61 | private void initComponents() { 62 | 63 | bodyComboBox = new javax.swing.JComboBox<>(); 64 | bodyTypePanel = new javax.swing.JPanel(); 65 | nonePanel = new javax.swing.JPanel(); 66 | rawBodyPanel = new javax.swing.JPanel(); 67 | scrollPane = new javax.swing.JScrollPane(); 68 | editorPane = new javax.swing.JEditorPane(); 69 | 70 | setPreferredSize(new java.awt.Dimension(800, 144)); 71 | 72 | bodyComboBox.setModel(new javax.swing.DefaultComboBoxModel<>(new String[] { "None", "Text", "JSON", "XML" })); 73 | bodyComboBox.setEnabled(false); 74 | bodyComboBox.setPreferredSize(new java.awt.Dimension(88, 22)); 75 | bodyComboBox.addActionListener(new java.awt.event.ActionListener() { 76 | public void actionPerformed(java.awt.event.ActionEvent evt) { 77 | bodyComboBoxActionPerformed(evt); 78 | } 79 | }); 80 | 81 | bodyTypePanel.setPreferredSize(new java.awt.Dimension(200, 100)); 82 | bodyTypePanel.setLayout(new java.awt.CardLayout()); 83 | 84 | javax.swing.GroupLayout nonePanelLayout = new javax.swing.GroupLayout(nonePanel); 85 | nonePanel.setLayout(nonePanelLayout); 86 | nonePanelLayout.setHorizontalGroup( 87 | nonePanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) 88 | .addGap(0, 637, Short.MAX_VALUE) 89 | ); 90 | nonePanelLayout.setVerticalGroup( 91 | nonePanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) 92 | .addGap(0, 138, Short.MAX_VALUE) 93 | ); 94 | 95 | bodyTypePanel.add(nonePanel, "None"); 96 | 97 | editorPane.setEditorKit(new StyledEditorKit()); 98 | scrollPane.setViewportView(editorPane); 99 | 100 | javax.swing.GroupLayout rawBodyPanelLayout = new javax.swing.GroupLayout(rawBodyPanel); 101 | rawBodyPanel.setLayout(rawBodyPanelLayout); 102 | rawBodyPanelLayout.setHorizontalGroup( 103 | rawBodyPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) 104 | .addComponent(scrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, 637, Short.MAX_VALUE) 105 | ); 106 | rawBodyPanelLayout.setVerticalGroup( 107 | rawBodyPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) 108 | .addComponent(scrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, 138, Short.MAX_VALUE) 109 | ); 110 | 111 | bodyTypePanel.add(rawBodyPanel, "Raw"); 112 | 113 | javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); 114 | this.setLayout(layout); 115 | layout.setHorizontalGroup( 116 | layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) 117 | .addGroup(layout.createSequentialGroup() 118 | .addContainerGap() 119 | .addComponent(bodyComboBox, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) 120 | .addGap(69, 69, 69) 121 | .addComponent(bodyTypePanel, javax.swing.GroupLayout.DEFAULT_SIZE, 637, Short.MAX_VALUE)) 122 | ); 123 | layout.setVerticalGroup( 124 | layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) 125 | .addGroup(layout.createSequentialGroup() 126 | .addContainerGap() 127 | .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) 128 | .addComponent(bodyTypePanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) 129 | .addGroup(layout.createSequentialGroup() 130 | .addComponent(bodyComboBox, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) 131 | .addContainerGap(116, Short.MAX_VALUE)))) 132 | ); 133 | }// //GEN-END:initComponents 134 | 135 | private void bodyComboBoxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_bodyComboBoxActionPerformed 136 | CardLayout cardLayout = (CardLayout) bodyTypePanel.getLayout(); 137 | 138 | String selectedItem = bodyComboBox.getSelectedItem().toString(); 139 | 140 | if (selectedItem.equals("Text") || selectedItem.equals("JSON") || selectedItem.equals("XML")) { 141 | selectedItem = "Raw"; 142 | } 143 | 144 | cardLayout.show(bodyTypePanel, selectedItem); 145 | }//GEN-LAST:event_bodyComboBoxActionPerformed 146 | 147 | 148 | // Variables declaration - do not modify//GEN-BEGIN:variables 149 | private javax.swing.JComboBox bodyComboBox; 150 | private javax.swing.JPanel bodyTypePanel; 151 | private javax.swing.JEditorPane editorPane; 152 | private javax.swing.JPanel nonePanel; 153 | private javax.swing.JPanel rawBodyPanel; 154 | private javax.swing.JScrollPane scrollPane; 155 | // End of variables declaration//GEN-END:variables 156 | } 157 | -------------------------------------------------------------------------------- /src/main/java/com/javierllorente/netbeans/rest/client/ui/BodyPanel.form: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 |
141 | -------------------------------------------------------------------------------- /src/main/java/com/javierllorente/netbeans/rest/client/http/editor/syntax/coloring/HTTPLanguage.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 Christian Lenz . 3 | * 4 | * Licensed to the Apache Software Foundation (ASF) under one 5 | * or more contributor license agreements. See the NOTICE file 6 | * distributed with this work for additional information 7 | * regarding copyright ownership. The ASF licenses this file 8 | * to you under the Apache License, Version 2.0 (the 9 | * "License"); you may not use this file except in compliance 10 | * with the License. You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, 15 | * software distributed under the License is distributed on an 16 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 | * KIND, either express or implied. See the License for the 18 | * specific language governing permissions and limitations 19 | * under the License. 20 | */ 21 | package com.javierllorente.netbeans.rest.client.http.editor.syntax.coloring; 22 | 23 | import com.javierllorente.netbeans.rest.client.http.editor.navigator.HTTPLangStructureScanner; 24 | import com.javierllorente.netbeans.rest.client.http.editor.syntax.HTTPLangLexer; 25 | import com.javierllorente.netbeans.rest.client.http.editor.syntax.HTTPLangParser; 26 | import com.javierllorente.netbeans.rest.client.http.editor.syntax.HTTPLangParserResult; 27 | import com.javierllorente.netbeans.rest.client.http.editor.syntax.coloring.semantic.HTTPSemanticAnalyzer; 28 | import java.util.Collection; 29 | import java.util.EnumSet; 30 | import org.netbeans.api.lexer.Language; 31 | import org.netbeans.core.spi.multiview.MultiViewElement; 32 | import org.netbeans.core.spi.multiview.text.MultiViewEditorElement; 33 | import org.netbeans.modules.csl.api.SemanticAnalyzer; 34 | import org.netbeans.modules.csl.api.StructureScanner; 35 | import org.netbeans.modules.csl.spi.DefaultLanguageConfig; 36 | import org.netbeans.modules.csl.spi.LanguageRegistration; 37 | import org.netbeans.modules.parsing.spi.Parser; 38 | import org.netbeans.spi.lexer.LanguageHierarchy; 39 | import org.netbeans.spi.lexer.Lexer; 40 | import org.netbeans.spi.lexer.LexerRestartInfo; 41 | import org.openide.awt.ActionID; 42 | import org.openide.awt.ActionReference; 43 | import org.openide.awt.ActionReferences; 44 | import org.openide.filesystems.MIMEResolver; 45 | import org.openide.util.*; 46 | import org.openide.windows.TopComponent; 47 | 48 | /** 49 | * 50 | * @author Christian Lenz 51 | */ 52 | @NbBundle.Messages( 53 | "HTTPResolver=HTTP File" 54 | ) 55 | @MIMEResolver.ExtensionRegistration( 56 | displayName = "#HTTPResolver", 57 | extension = {"http", "HTTP", "rest", "REST"}, 58 | mimeType = HTTPLanguage.MIME_TYPE, 59 | position = 315 60 | ) 61 | 62 | @ActionReferences({ 63 | @ActionReference( 64 | path = "Loaders/text/x-http/Actions", 65 | id = @ActionID(category = "System", id = "org.openide.actions.OpenAction"), 66 | position = 100, 67 | separatorAfter = 200 68 | ), 69 | @ActionReference( 70 | path = "Loaders/text/x-http/Actions", 71 | id = @ActionID(category = "Edit", id = "org.openide.actions.CutAction"), 72 | position = 300 73 | ), 74 | @ActionReference( 75 | path = "Loaders/text/x-http/Actions", 76 | id = @ActionID(category = "Edit", id = "org.openide.actions.CopyAction"), 77 | position = 400 78 | ), 79 | @ActionReference( 80 | path = "Loaders/text/x-http/Actions", 81 | id = @ActionID(category = "Edit", id = "org.openide.actions.PasteAction"), 82 | position = 550, 83 | separatorAfter = 600 84 | ), 85 | @ActionReference( 86 | path = "Loaders/text/x-http/Actions", 87 | id = @ActionID(category = "Edit", id = "org.openide.actions.DeleteAction"), 88 | position = 700 89 | ), 90 | @ActionReference( 91 | path = "Loaders/text/x-http/Actions", 92 | id = @ActionID(category = "System", id = "org.openide.actions.RenameAction"), 93 | position = 800, 94 | separatorAfter = 900 95 | ), 96 | @ActionReference( 97 | path = "Loaders/text/x-http/Actions", 98 | id = @ActionID(category = "System", id = "org.openide.actions.SaveAsTemplateAction"), 99 | position = 1000, 100 | separatorAfter = 1100 101 | ), 102 | @ActionReference( 103 | path = "Loaders/text/x-http/Actions", 104 | id = @ActionID(category = "System", id = "org.openide.actions.FileSystemAction"), 105 | position = 1200, 106 | separatorAfter = 1300 107 | ), 108 | @ActionReference( 109 | path = "Loaders/text/x-http/Actions", 110 | id = @ActionID(category = "System", id = "org.openide.actions.ToolsAction"), 111 | position = 1400 112 | ), 113 | @ActionReference( 114 | path = "Loaders/text/x-http/Actions", 115 | id = @ActionID(category = "System", id = "org.openide.actions.PropertiesAction"), 116 | position = 1500 117 | ) 118 | }) 119 | @LanguageRegistration(mimeType = HTTPLanguage.MIME_TYPE, useMultiview = true) 120 | public class HTTPLanguage extends DefaultLanguageConfig { 121 | 122 | public static final String MIME_TYPE = "text/x-http"; 123 | 124 | @Override 125 | public Language getLexerLanguage() { 126 | return LANGUAGE; 127 | } 128 | 129 | @Override 130 | public String getLineCommentPrefix() { 131 | return "#"; // NOI18N 132 | } 133 | 134 | @Override 135 | public String getDisplayName() { 136 | return "Http"; //NOI18N 137 | } 138 | 139 | @Override 140 | public SemanticAnalyzer getSemanticAnalyzer() { 141 | return new HTTPSemanticAnalyzer(); 142 | } 143 | 144 | @Override 145 | public Parser getParser() { 146 | return new HTTPLangParser(); 147 | } 148 | 149 | @Override 150 | public StructureScanner getStructureScanner() { 151 | return new HTTPLangStructureScanner(); 152 | } 153 | 154 | @Override 155 | public boolean hasStructureScanner() { 156 | return true; 157 | } 158 | 159 | public static final Language LANGUAGE = new LanguageHierarchy() { 160 | @Override 161 | protected Collection createTokenIds() { 162 | return EnumSet.allOf(HTTPTokenId.class); 163 | } 164 | 165 | @Override 166 | protected Lexer createLexer(LexerRestartInfo info) { 167 | return new HTTPLangLexer(info); 168 | } 169 | 170 | @Override 171 | protected String mimeType() { 172 | return HTTPLanguage.MIME_TYPE; 173 | } 174 | }.language(); 175 | 176 | @NbBundle.Messages("Source=&Source") 177 | @MultiViewElement.Registration( 178 | displayName = "#Source", 179 | persistenceType = TopComponent.PERSISTENCE_ONLY_OPENED, 180 | mimeType = HTTPLanguage.MIME_TYPE, 181 | preferredID = "http.source", 182 | position = 100 183 | ) 184 | public static MultiViewEditorElement createMultiViewEditorElement(Lookup context) { 185 | return new MultiViewEditorElement(context); 186 | } 187 | } 188 | -------------------------------------------------------------------------------- /src/main/antlr4/HTTPParser.g4: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 Christian Lenz . 3 | * 4 | * Licensed to the Apache Software Foundation (ASF) under one 5 | * or more contributor license agreements. See the NOTICE file 6 | * distributed with this work for additional information 7 | * regarding copyright ownership. The ASF licenses this file 8 | * to you under the Apache License, Version 2.0 (the 9 | * "License"); you may not use this file except in compliance 10 | * with the License. You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, 15 | * software distributed under the License is distributed on an 16 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 | * KIND, either express or implied. See the License for the 18 | * specific language governing permissions and limitations 19 | * under the License. 20 | */ 21 | 22 | /** 23 | * 24 | * @author Christian Lenz 25 | */ 26 | parser grammar HTTPParser; 27 | 28 | options { 29 | tokenVocab = HTTPLexer; 30 | } 31 | 32 | httpRequestsFile: 33 | (blank | COMMENT)* 34 | requestBlock? 35 | ((blank | COMMENT)* REQUEST_SEPARATOR (blank | COMMENT)* requestBlock?)* 36 | (blank | COMMENT)* 37 | EOF; 38 | 39 | requestBlock: blank* (request | invalidBodyWithoutRequest); 40 | 41 | request: 42 | requestLine requestHeaders? requestBodySection? 43 | | requestLineWithBody; 44 | 45 | requestHeaders: (headerLine)+; 46 | 47 | headerLine: 48 | (WS* header (NEWLINE | {_input.LA(1) == BODY_START_WITH_BLANK || _input.LA(1) == GENERIC_BODY_START || _input.LA(1) == BODY_START_NO_BLANK || _input.LA(1) == EOF}?) 49 | | WS* invalidHeaderLine); 50 | 51 | invalidBodyWithoutRequest: 52 | { 53 | notifyErrorListeners("Request body requires a request line"); 54 | } 55 | (BODY_START_WITH_BLANK | BODY_START_NO_BLANK | GENERIC_BODY_START) 56 | .*? 57 | (REQUEST_SEPARATOR | EOF); 58 | 59 | invalidHeaderLine: 60 | {_input.LA(2) != COLON && _input.LA(1) != COMMENT && _input.LA(1) != NEWLINE}? 61 | invalidHeaderContent 62 | { 63 | notifyErrorListeners("Unknown HTTP header"); 64 | } 65 | NEWLINE?; 66 | 67 | invalidHeaderContent: ~(NEWLINE | REQUEST_SEPARATOR | BODY_START_WITH_BLANK | BODY_START_NO_BLANK | GENERIC_BODY_START | COMMENT | EOF | WS)+; 68 | 69 | requestBodySection: 70 | (WS | NEWLINE | COMMENT)* requestBody; 71 | 72 | requestBody: 73 | bodyWithStarter 74 | | directBodyContent; 75 | 76 | bodyWithStarter: 77 | (BODY_START_WITH_BLANK | BODY_START_NO_BLANK | GENERIC_BODY_START) bodyContent; 78 | 79 | // Pure body content in BODY_CONTENT mode 80 | bodyContent: 81 | (.)*? CLOSE_BLOCK_BRAKET // Match anything until } 82 | | (.)* ; // Match anything until end 83 | 84 | // Direct body content for special cases (uppercase, etc.) 85 | directBodyContent: 86 | (~(REQUEST_SEPARATOR | EOF))+ ; 87 | 88 | // Json structure for requestBody 89 | jsonObject: 90 | OPEN_BLOCK_BRAKET space (pair (space COMMA space pair)*)? space 91 | (CLOSE_BLOCK_BRAKET | { notifyErrorListeners("Missing closing bracket '}' in JSON object"); }); 92 | 93 | pair: 94 | jsonString space COLON space jsonValue; 95 | 96 | jsonArray: 97 | OPEN_BRAKET space (jsonValue (space COMMA space jsonValue)*)? space 98 | (CLOSE_BRAKET | { notifyErrorListeners("Missing closing bracket ']' in JSON array"); }); 99 | 100 | jsonValue: 101 | jsonString 102 | | jsonNumber 103 | | jsonObject 104 | | jsonArray 105 | | jsonBareWord; 106 | 107 | jsonString: STRING | QUOTE jsonStringContent? QUOTE; 108 | 109 | jsonStringContent: jsonStringChar+; 110 | 111 | jsonStringChar: 112 | ALPHA_CHARS 113 | | DIGITS 114 | | DASH 115 | | UNDERSCORE 116 | | DOT 117 | | SLASH 118 | | COLON 119 | | AMPERSAND 120 | | PERCENT 121 | | QUESTION_MARK 122 | | HASH 123 | | WS 124 | | OPEN_BRAKET 125 | | CLOSE_BRAKET 126 | | OPEN_BLOCK_BRAKET 127 | | CLOSE_BLOCK_BRAKET 128 | | ASTERISK 129 | | EQUAL 130 | | SCHEME_SEPARATOR 131 | | COMMA; 132 | 133 | jsonNumber: NUMBER; 134 | 135 | jsonBareWord: ALPHA_CHARS; 136 | 137 | space: (NEWLINE | WS)*; 138 | 139 | header: headerField; 140 | 141 | headerField: headerFieldName COLON WS* headerFieldValue; 142 | 143 | headerFieldName: (ALPHA_CHARS | DASH | UNDERSCORE)+; 144 | headerFieldValue: (~(NEWLINE | BODY_START_WITH_BLANK | BODY_START_NO_BLANK | GENERIC_BODY_START))*; 145 | 146 | fieldName: QUOTE (ALPHA_CHARS | DASH | UNDERSCORE) QUOTE; 147 | 148 | fieldValue: QUOTE? (ALPHA_CHARS | DASH | UNDERSCORE | DIGITS | SLASH)+ QUOTE?; 149 | 150 | blank: NEWLINE | WS; 151 | 152 | requestLine: 153 | (METHOD WS+)? requestTarget (WS+ httpVersion)? WS* NEWLINE?; 154 | 155 | requestLineWithBody: 156 | (METHOD WS+)? requestTarget (WS+ httpVersion)? BODY_START_WITH_BLANK bodyContent; 157 | 158 | requestTarget: originForm | absoluteForm | asteriskForm; 159 | 160 | originForm: slashPathPart (queryPart | fragmentPart)*; 161 | 162 | absolutePath: SLASH (pathSeparator segment)+; 163 | 164 | segment: (ALPHA_CHARS | DIGITS | DASH | UNDERSCORE)+; 165 | 166 | pathSeparator: SLASH WS+; 167 | 168 | absoluteForm: (SCHEME SCHEME_SEPARATOR)? (hostPort | ipAddress) ( 169 | slashPathPart 170 | | queryPart 171 | | fragmentPart 172 | )*; 173 | 174 | hostPort: host (COLON DIGITS)*; 175 | 176 | host: segment (DOT segment)*; 177 | 178 | ipAddress: ipv4Address | ipv6Address; 179 | 180 | ipv4Address: 181 | DIGITS DOT DIGITS DOT DIGITS DOT DIGITS (COLON DIGITS)*; 182 | 183 | ipv6Address: 184 | OPEN_BRAKET ipv6Literal CLOSE_BRAKET (COLON DIGITS)*; 185 | 186 | ipv6Literal: fullIPv6 | compressedIPv6; 187 | 188 | fullIPv6: 189 | hextet COLON hextet COLON hextet COLON hextet COLON hextet COLON hextet COLON hextet COLON 190 | hextet; 191 | 192 | compressedIPv6: (hextet (COLON hextet)*)? COLON COLON ( 193 | hextet (COLON hextet)* 194 | )?; 195 | 196 | hextet: hexa+; 197 | 198 | hexa: (DIGITS | ALPHA_CHARS)+; 199 | 200 | slashPathPart: SLASH (pathSegment (SLASH pathSegment)* SLASH?)?; 201 | 202 | pathSegment: (ALPHA_CHARS | DIGITS) (DOT | DASH | UNDERSCORE | ALPHA_CHARS | DIGITS)*; 203 | 204 | asteriskForm: ASTERISK; 205 | 206 | queryPart: QUESTION_MARK queryContent?; 207 | 208 | queryContent: 209 | queryParam (AMPERSAND queryParam)*; 210 | 211 | queryParam: 212 | queryKey (EQUAL queryValue)?; 213 | 214 | queryKey: 215 | (ALPHA_CHARS | DIGITS | DASH | UNDERSCORE | DOT | PERCENT)+; 216 | 217 | queryValue: 218 | (ALPHA_CHARS 219 | | DIGITS 220 | | DOT 221 | | SLASH 222 | | COLON 223 | | DASH 224 | | UNDERSCORE 225 | | PERCENT 226 | | EQUAL 227 | | QUESTION_MARK 228 | | HASH 229 | | OPEN_BRAKET 230 | | CLOSE_BRAKET 231 | | SCHEME_SEPARATOR 232 | | SCHEME 233 | | COMMA 234 | )+; 235 | 236 | fragmentPart: HASH fragmentContent; 237 | 238 | fragmentContent: ( 239 | ALPHA_CHARS 240 | | DIGITS 241 | | DOT 242 | | SLASH 243 | | COLON 244 | | PERCENT 245 | )+; 246 | 247 | httpVersion: 248 | HTTP_PROTOCOL 249 | (SLASH | { notifyErrorListeners("Missing '/' in HTTP version"); }) 250 | (versionNumber | { notifyErrorListeners("Invalid HTTP version number"); }); // HTTP/1.1 251 | 252 | versionNumber: DIGITS (DOT DIGITS)?; 253 | 254 | invalidContent: ~(WS | NEWLINE | REQUEST_SEPARATOR | METHOD)+; 255 | -------------------------------------------------------------------------------- /src/main/java/com/javierllorente/netbeans/rest/client/http/editor/sidebar/DrawingPanel.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 Christian Lenz . 3 | * 4 | * Licensed to the Apache Software Foundation (ASF) under one 5 | * or more contributor license agreements. See the NOTICE file 6 | * distributed with this work for additional information 7 | * regarding copyright ownership. The ASF licenses this file 8 | * to you under the Apache License, Version 2.0 (the 9 | * "License"); you may not use this file except in compliance 10 | * with the License. You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, 15 | * software distributed under the License is distributed on an 16 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 | * KIND, either express or implied. See the License for the 18 | * specific language governing permissions and limitations 19 | * under the License. 20 | */ 21 | package com.javierllorente.netbeans.rest.client.http.editor.sidebar; 22 | 23 | import com.javierllorente.netbeans.rest.client.http.editor.sidebar.request.IRequestProcessor; 24 | import com.javierllorente.netbeans.rest.client.http.editor.sidebar.request.Request; 25 | import java.awt.Color; 26 | import java.awt.Cursor; 27 | import java.awt.Dimension; 28 | import java.awt.Graphics; 29 | import java.awt.Graphics2D; 30 | import java.awt.Rectangle; 31 | import java.awt.RenderingHints; 32 | import java.awt.geom.Rectangle2D; 33 | import java.util.ArrayList; 34 | import java.util.List; 35 | import javax.swing.JPanel; 36 | import javax.swing.event.DocumentEvent; 37 | import javax.swing.event.DocumentListener; 38 | import javax.swing.text.BadLocationException; 39 | import javax.swing.text.JTextComponent; 40 | import javax.swing.text.StyledDocument; 41 | import org.netbeans.editor.Utilities; 42 | import org.openide.text.NbDocument; 43 | import org.openide.util.Exceptions; 44 | 45 | /** 46 | * 47 | * @author Chrizzly 48 | */ 49 | public class DrawingPanel extends JPanel { 50 | 51 | private final JTextComponent textComponent; 52 | private final StyledDocument document; 53 | private final List taskAreas; 54 | private final IRequestProcessor taskProcessor; // Nur ein TaskProcessor 55 | private final ActionPopup actionPopup; 56 | 57 | public DrawingPanel(JTextComponent editor, IRequestProcessor processor) { // Nur ein TaskProcessor 58 | this.textComponent = editor; 59 | this.document = (StyledDocument) editor.getDocument(); 60 | this.taskAreas = new ArrayList<>(); 61 | this.taskProcessor = processor; // Setze den übergebenen TaskProcessor 62 | this.actionPopup = new ActionPopup(taskProcessor); 63 | 64 | document.addDocumentListener(new DocumentListener() { 65 | @Override 66 | public void insertUpdate(DocumentEvent e) { 67 | updateSidebar(); 68 | } 69 | 70 | @Override 71 | public void removeUpdate(DocumentEvent e) { 72 | updateSidebar(); 73 | } 74 | 75 | @Override 76 | public void changedUpdate(DocumentEvent e) { 77 | updateSidebar(); 78 | } 79 | }); 80 | 81 | this.setPreferredSize(new Dimension(20, HEIGHT)); 82 | try { 83 | // Initialisiere Request-Liste vom TaskProcessor 84 | taskProcessor.updateRequestsList(document.getText(0, document.getLength())); 85 | } catch (BadLocationException ex) { 86 | Exceptions.printStackTrace(ex); 87 | } 88 | 89 | addMouseListener(new PopupListener()); 90 | addMouseMotionListener(new CursorListener()); 91 | } 92 | 93 | @Override 94 | protected void paintComponent(Graphics g) { 95 | super.paintComponent(g); 96 | Utilities.runViewHierarchyTransaction(textComponent, true, () -> paintComponentUnderLock(g)); 97 | } 98 | 99 | private void paintComponentUnderLock(Graphics g) { 100 | Rectangle clip = g.getClipBounds(); 101 | g.setColor(textComponent.getBackground()); 102 | 103 | if (clip != null) { 104 | g.fillRect(clip.x, clip.y, clip.width, clip.height); 105 | } 106 | 107 | if (taskProcessor == null) { 108 | return; 109 | } 110 | 111 | try { 112 | drawPanel(textComponent, g); 113 | } catch (BadLocationException ex) { 114 | ex.printStackTrace(); 115 | } 116 | } 117 | 118 | private void drawPanel(JTextComponent component, Graphics g) throws BadLocationException { 119 | Graphics2D g2 = (Graphics2D) g; 120 | g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); 121 | g2.setColor(new Color(0, 170, 0)); 122 | 123 | taskAreas.clear(); 124 | List tasks = taskProcessor.getRequests(); // Hol die Tasks vom TaskProcessor 125 | tasks.sort((t1, t2) -> Integer.compare(t1.getLineNumber(), t2.getLineNumber())); 126 | 127 | for (Request task : tasks) { 128 | int lineNumber = task.getLineNumber(); 129 | int startOffset = NbDocument.findLineOffset(document, lineNumber); 130 | Rectangle2D rect = component.modelToView2D(startOffset); 131 | 132 | if (rect != null) { 133 | int[] xPoints = {4, 10, 4}; 134 | int[] yPoints = {(int) rect.getY(), (int) rect.getY() + 7, (int) rect.getY() + 14}; 135 | g2.fillPolygon(xPoints, yPoints, 3); 136 | taskAreas.add(new Rectangle2D.Float(4, (float) rect.getY(), 6, 14)); 137 | } 138 | } 139 | } 140 | 141 | private void updateSidebar() { 142 | try { 143 | String text = document.getText(0, document.getLength()); 144 | updateSidebar(text); 145 | } catch (BadLocationException ex) { 146 | ex.printStackTrace(); 147 | } 148 | } 149 | 150 | private void updateSidebar(String text) { 151 | taskProcessor.updateRequestsList(text); 152 | revalidate(); 153 | repaint(); 154 | } 155 | 156 | private class PopupListener extends java.awt.event.MouseAdapter { 157 | 158 | @Override 159 | public void mousePressed(java.awt.event.MouseEvent e) { 160 | if (e.getButton() == java.awt.event.MouseEvent.BUTTON1) { 161 | showPopup(e); 162 | } 163 | } 164 | 165 | private void showPopup(java.awt.event.MouseEvent e) { 166 | for (int i = 0; i < taskProcessor.getRequests().size(); i++) { 167 | Request task = taskProcessor.getRequests().get(i); 168 | Rectangle2D rect = taskAreas.get(i); 169 | 170 | if (rect.contains(e.getPoint())) { 171 | javax.swing.JPopupMenu popupMenu = actionPopup.createPopupMenu(task); 172 | popupMenu.show(e.getComponent(), e.getX(), e.getY()); 173 | break; 174 | } 175 | } 176 | } 177 | } 178 | 179 | private class CursorListener extends java.awt.event.MouseAdapter { 180 | 181 | @Override 182 | public void mouseMoved(java.awt.event.MouseEvent e) { 183 | for (Rectangle2D rect : taskAreas) { 184 | if (rect.contains(e.getPoint())) { 185 | setCursor(Cursor.getPredefinedCursor(java.awt.Cursor.HAND_CURSOR)); 186 | return; 187 | } 188 | } 189 | setCursor(Cursor.getDefaultCursor()); 190 | } 191 | } 192 | } 193 | -------------------------------------------------------------------------------- /src/main/java/com/javierllorente/netbeans/rest/client/ui/TablePanel.form: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 |
66 |
67 | 68 | 69 | 70 | 71 | <Editor/> 72 | <Renderer/> 73 | </Column> 74 | <Column maxWidth="-1" minWidth="-1" prefWidth="-1" resizable="true"> 75 | <Title/> 76 | <Editor/> 77 | <Renderer/> 78 | </Column> 79 | <Column maxWidth="-1" minWidth="-1" prefWidth="-1" resizable="true"> 80 | <Title/> 81 | <Editor/> 82 | <Renderer/> 83 | </Column> 84 | </TableColumnModel> 85 | </Property> 86 | <Property name="tableHeader" type="javax.swing.table.JTableHeader" editor="org.netbeans.modules.form.editors2.JTableHeaderEditor"> 87 | <TableHeader reorderingAllowed="false" resizingAllowed="true"/> 88 | </Property> 89 | </Properties> 90 | <AuxValues> 91 | <AuxValue name="JavaCodeGenerator_InitCodePost" type="java.lang.String" value="table.getSelectionModel().addListSelectionListener((ListSelectionEvent lse) -> { if (!lse.getValueIsAdjusting()) { logger.info("Table selection changed"); removeButton.setEnabled(!table.getSelectionModel().isSelectionEmpty()); } }); table.putClientProperty("terminateEditOnFocusLost", Boolean.TRUE);"/> 92 | </AuxValues> 93 | </Component> 94 | </SubComponents> 95 | </Container> 96 | <Container class="javax.swing.JPanel" name="buttonPanel"> 97 | 98 | <Layout> 99 | <DimensionLayout dim="0"> 100 | <Group type="103" groupAlignment="0" attributes="0"> 101 | <Group type="102" attributes="0"> 102 | <Group type="103" groupAlignment="0" attributes="0"> 103 | <Component id="addButton" min="-2" max="-2" attributes="0"/> 104 | <Component id="removeButton" min="-2" max="-2" attributes="0"/> 105 | </Group> 106 | <EmptySpace min="0" pref="35" max="32767" attributes="0"/> 107 | </Group> 108 | </Group> 109 | </DimensionLayout> 110 | <DimensionLayout dim="1"> 111 | <Group type="103" groupAlignment="0" attributes="0"> 112 | <Group type="102" alignment="0" attributes="0"> 113 | <Component id="addButton" min="-2" max="-2" attributes="0"/> 114 | <EmptySpace max="-2" attributes="0"/> 115 | <Component id="removeButton" min="-2" max="-2" attributes="0"/> 116 | <EmptySpace min="0" pref="0" max="32767" attributes="0"/> 117 | </Group> 118 | </Group> 119 | </DimensionLayout> 120 | </Layout> 121 | <SubComponents> 122 | <Component class="javax.swing.JButton" name="addButton"> 123 | <Properties> 124 | <Property name="text" type="java.lang.String" value="Add"/> 125 | </Properties> 126 | <Events> 127 | <EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="addButtonActionPerformed"/> 128 | </Events> 129 | </Component> 130 | <Component class="javax.swing.JButton" name="removeButton"> 131 | <Properties> 132 | <Property name="text" type="java.lang.String" value="Remove"/> 133 | <Property name="enabled" type="boolean" value="false"/> 134 | </Properties> 135 | <Events> 136 | <EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="removeButtonActionPerformed"/> 137 | </Events> 138 | </Component> 139 | </SubComponents> 140 | </Container> 141 | </SubComponents> 142 | </Form> 143 | -------------------------------------------------------------------------------- /src/main/java/com/javierllorente/netbeans/rest/client/parsers/Parser.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one 3 | * or more contributor license agreements. See the NOTICE file 4 | * distributed with this work for additional information 5 | * regarding copyright ownership. The ASF licenses this file 6 | * to you under the Apache License, Version 2.0 (the 7 | * "License"); you may not use this file except in compliance 8 | * with the License. You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, 13 | * software distributed under the License is distributed on an 14 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | * KIND, either express or implied. See the License for the 16 | * specific language governing permissions and limitations 17 | * under the License. 18 | */ 19 | package com.javierllorente.netbeans.rest.client.parsers; 20 | 21 | import java.util.LinkedList; 22 | import java.util.List; 23 | import java.util.regex.Matcher; 24 | import java.util.regex.Pattern; 25 | 26 | /** 27 | * 28 | * @author Jan Lahoda 29 | */ 30 | public final class Parser { 31 | 32 | private enum STATE { 33 | START, H, HT, F, HTT_FT, 34 | HTTP_FTP, 35 | HTTPS, 36 | HTTPC, // // (ht|f)tp(s?) 37 | HTTPCS, // (ht|f)tp(s?):/ 38 | FI, 39 | FIL, 40 | FILE, 41 | N, NB, NBF_F, NBFS, 42 | END // (ht|f)tp(s?):// 43 | } 44 | 45 | private Parser() {} 46 | 47 | public static Iterable<int[]> recognizeURLs(CharSequence text) { 48 | List<int[]> result = new LinkedList<int[]>(); 49 | STATE state = STATE.START; 50 | int lastURLStart = -1; 51 | 52 | OUTER: for (int cntr = 0; cntr < text.length(); cntr++) { 53 | char ch = text.charAt(cntr); 54 | 55 | if (state == STATE.END) { 56 | if (Character.isLetterOrDigit(ch)) { 57 | continue OUTER; 58 | } 59 | 60 | switch (ch) { 61 | case '/': case '.': case '?': case '+': //NOI18N 62 | case '%': case '_': case '~': case '=': //NOI18N 63 | case '\\':case '&': case '$': case '-': //NOI18N 64 | case '#': case ',': case ':': case ';': //NOI18N 65 | case '!': case '(': case ')': case '@': //NOI18N 66 | continue OUTER; 67 | } 68 | 69 | assert lastURLStart != (-1); 70 | result.add(new int[] {lastURLStart, cntr}); 71 | 72 | lastURLStart = (-1); 73 | state = STATE.START; 74 | continue OUTER; 75 | } 76 | 77 | switch (ch) { 78 | case 'h': //NOI18N 79 | if (state == STATE.START) { 80 | lastURLStart = cntr; 81 | state = STATE.H; 82 | continue OUTER; 83 | } 84 | break; 85 | case 'n': //NOI18N 86 | if (state == STATE.START) { 87 | lastURLStart = cntr; 88 | state = STATE.N; 89 | continue OUTER; 90 | } 91 | break; 92 | case 't': //NOI18N 93 | if (state == STATE.H) { 94 | state = STATE.HT; 95 | continue OUTER; 96 | } else if (state == STATE.HT) { 97 | state = STATE.HTT_FT; 98 | continue OUTER; 99 | } else if (state == STATE.F) { 100 | state = STATE.HTT_FT; 101 | continue OUTER; 102 | } 103 | break; 104 | case 'b': //NOI18N 105 | if (state == STATE.N) { 106 | state = STATE.NB; 107 | continue OUTER; 108 | } 109 | break; 110 | case 'f': //NOI18N 111 | if (state == STATE.START) { 112 | lastURLStart = cntr; 113 | state = STATE.F; 114 | continue OUTER; 115 | } else if (state == STATE.NB) { 116 | state = STATE.NBF_F; 117 | continue OUTER; 118 | } 119 | break; 120 | case 'i': //NOI18N 121 | if (state == STATE.F || state == STATE.NBF_F) { 122 | state = STATE.FI; 123 | continue OUTER; 124 | } 125 | case 'l': //NOI18N 126 | if (state == STATE.FI) { 127 | state = STATE.FIL; 128 | continue OUTER; 129 | } 130 | case 'e': //NOI18N 131 | if (state == STATE.FIL) { 132 | state = STATE.FILE; 133 | continue OUTER; 134 | } 135 | case 'p': //NOI18N 136 | if (state == STATE.HTT_FT) { 137 | state = STATE.HTTP_FTP; 138 | continue OUTER; 139 | } 140 | break; 141 | case 's': //NOI18N 142 | if (state == STATE.HTTP_FTP) { 143 | state = STATE.HTTPS; 144 | continue OUTER; 145 | } else if (state == STATE.NBF_F) { 146 | state = STATE.NBFS; 147 | continue OUTER; 148 | } 149 | break; 150 | case ':': //NOI18N 151 | if (state == STATE.HTTP_FTP || state == STATE.HTTPS || state == STATE.FILE || state == STATE.NBFS) { 152 | state = STATE.HTTPC; 153 | continue OUTER; 154 | } 155 | break; 156 | case '/' : //NOI18N 157 | if (state == STATE.HTTPC) { 158 | state = STATE.HTTPCS; 159 | continue OUTER; 160 | } else if (state == STATE.HTTPCS) { 161 | state = STATE.END; 162 | continue OUTER; 163 | } else if (state == STATE.NBFS) { 164 | state = STATE.END; 165 | continue OUTER; 166 | } 167 | break; 168 | } 169 | 170 | state = STATE.START; 171 | lastURLStart = (-1); 172 | } 173 | 174 | if (lastURLStart != (-1) && state == STATE.END) { 175 | result.add(new int[] {lastURLStart, text.length()}); 176 | } 177 | 178 | return result; 179 | } 180 | 181 | private static final Pattern URL_PATTERN = Pattern.compile("(ht|f|n)(tp(s?)|ile|bfs)://[0-9a-zA-Z/.?%+_~=\\\\&@$\\-#,:!/(/)]*"); //NOI18N 182 | 183 | public static Iterable<int[]> recognizeURLsREBased(CharSequence text) { 184 | Matcher m = URL_PATTERN.matcher(text); 185 | List<int[]> result = new LinkedList<int[]>(); 186 | 187 | while (m.find()) { 188 | result.add(new int[] {m.start(), m.start() + m.group(0).length()}); 189 | } 190 | 191 | return result; 192 | } 193 | 194 | private static final Pattern CHARSET = Pattern.compile("charset=([^;]+)(;|$)", Pattern.MULTILINE);//NOI18N 195 | public static String decodeContentType(String contentType) { 196 | if (contentType == null) return null; 197 | 198 | if (contentType != null) { 199 | Matcher m = CHARSET.matcher(contentType); 200 | 201 | if (m.find()) { 202 | return m.group(1); 203 | } 204 | } 205 | 206 | return null; 207 | } 208 | 209 | } 210 | -------------------------------------------------------------------------------- /src/main/java/com/javierllorente/netbeans/rest/client/ui/RestClientTopComponent.form: -------------------------------------------------------------------------------- 1 | <?xml version="1.0" encoding="UTF-8" ?> 2 | 3 | <Form version="1.6" maxVersion="1.9" type="org.netbeans.modules.form.forminfo.JPanelFormInfo"> 4 | <Properties> 5 | <Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor"> 6 | <Dimension value="[800, 600]"/> 7 | </Property> 8 | </Properties> 9 | <AuxValues> 10 | <AuxValue name="FormSettings_autoResourcing" type="java.lang.Integer" value="0"/> 11 | <AuxValue name="FormSettings_autoSetComponentName" type="java.lang.Boolean" value="false"/> 12 | <AuxValue name="FormSettings_generateFQN" type="java.lang.Boolean" value="true"/> 13 | <AuxValue name="FormSettings_generateMnemonicsCode" type="java.lang.Boolean" value="true"/> 14 | <AuxValue name="FormSettings_i18nAutoMode" type="java.lang.Boolean" value="false"/> 15 | <AuxValue name="FormSettings_layoutCodeTarget" type="java.lang.Integer" value="1"/> 16 | <AuxValue name="FormSettings_listenerGenerationStyle" type="java.lang.Integer" value="0"/> 17 | <AuxValue name="FormSettings_variablesLocal" type="java.lang.Boolean" value="false"/> 18 | <AuxValue name="FormSettings_variablesModifier" type="java.lang.Integer" value="2"/> 19 | </AuxValues> 20 | 21 | <Layout> 22 | <DimensionLayout dim="0"> 23 | <Group type="103" groupAlignment="0" attributes="0"> 24 | <Group type="102" alignment="0" attributes="0"> 25 | <EmptySpace max="-2" attributes="0"/> 26 | <Component id="splitPane" pref="794" max="32767" attributes="0"/> 27 | <EmptySpace max="-2" attributes="0"/> 28 | </Group> 29 | </Group> 30 | </DimensionLayout> 31 | <DimensionLayout dim="1"> 32 | <Group type="103" groupAlignment="0" attributes="0"> 33 | <Group type="102" alignment="0" attributes="0"> 34 | <EmptySpace max="-2" attributes="0"/> 35 | <Component id="splitPane" max="32767" attributes="0"/> 36 | <EmptySpace max="-2" attributes="0"/> 37 | </Group> 38 | </Group> 39 | </DimensionLayout> 40 | </Layout> 41 | <SubComponents> 42 | <Container class="javax.swing.JSplitPane" name="splitPane"> 43 | <Properties> 44 | <Property name="orientation" type="int" value="0"/> 45 | </Properties> 46 | 47 | <Layout class="org.netbeans.modules.form.compat2.layouts.support.JSplitPaneSupportLayout"/> 48 | <SubComponents> 49 | <Container class="javax.swing.JPanel" name="requestPanel"> 50 | <Properties> 51 | <Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor"> 52 | <Dimension value="[800, 240]"/> 53 | </Property> 54 | </Properties> 55 | <Constraints> 56 | <Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.support.JSplitPaneSupportLayout" value="org.netbeans.modules.form.compat2.layouts.support.JSplitPaneSupportLayout$JSplitPaneConstraintsDescription"> 57 | <JSplitPaneConstraints position="top"/> 58 | </Constraint> 59 | </Constraints> 60 | 61 | <Layout> 62 | <DimensionLayout dim="0"> 63 | <Group type="103" groupAlignment="0" attributes="0"> 64 | <Group type="102" attributes="0"> 65 | <Component id="requestTabbedPane" pref="794" max="32767" attributes="0"/> 66 | <EmptySpace max="-2" attributes="0"/> 67 | </Group> 68 | <Group type="102" attributes="0"> 69 | <EmptySpace max="-2" attributes="0"/> 70 | <Component id="jButton1" min="-2" max="-2" attributes="0"/> 71 | <EmptySpace max="-2" attributes="0"/> 72 | <Component id="urlPanel" pref="0" max="32767" attributes="0"/> 73 | </Group> 74 | </Group> 75 | </DimensionLayout> 76 | <DimensionLayout dim="1"> 77 | <Group type="103" groupAlignment="0" attributes="0"> 78 | <Group type="102" alignment="0" attributes="0"> 79 | <EmptySpace max="-2" attributes="0"/> 80 | <Group type="103" groupAlignment="1" attributes="0"> 81 | <Component id="urlPanel" min="-2" max="-2" attributes="0"/> 82 | <Component id="jButton1" min="-2" max="-2" attributes="0"/> 83 | </Group> 84 | <EmptySpace min="-2" pref="3" max="-2" attributes="0"/> 85 | <Component id="requestTabbedPane" pref="211" max="32767" attributes="0"/> 86 | </Group> 87 | </Group> 88 | </DimensionLayout> 89 | </Layout> 90 | <SubComponents> 91 | <Container class="javax.swing.JTabbedPane" name="requestTabbedPane"> 92 | <Properties> 93 | <Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor"> 94 | <Dimension value="[705, 150]"/> 95 | </Property> 96 | </Properties> 97 | <AccessibilityProperties> 98 | <Property name="AccessibleContext.accessibleName" type="java.lang.String" value=""/> 99 | </AccessibilityProperties> 100 | 101 | <Layout class="org.netbeans.modules.form.compat2.layouts.support.JTabbedPaneSupportLayout"/> 102 | <SubComponents> 103 | <Component class="com.javierllorente.netbeans.rest.client.ui.TablePanel" name="paramsPanel"> 104 | <Constraints> 105 | <Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.support.JTabbedPaneSupportLayout" value="org.netbeans.modules.form.compat2.layouts.support.JTabbedPaneSupportLayout$JTabbedPaneConstraintsDescription"> 106 | <JTabbedPaneConstraints tabName="Params"> 107 | <Property name="tabTitle" type="java.lang.String" value="Params"/> 108 | </JTabbedPaneConstraints> 109 | </Constraint> 110 | </Constraints> 111 | </Component> 112 | <Component class="com.javierllorente.netbeans.rest.client.ui.AuthPanel" name="authPanel"> 113 | <Constraints> 114 | <Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.support.JTabbedPaneSupportLayout" value="org.netbeans.modules.form.compat2.layouts.support.JTabbedPaneSupportLayout$JTabbedPaneConstraintsDescription"> 115 | <JTabbedPaneConstraints tabName="Authorisation"> 116 | <Property name="tabTitle" type="java.lang.String" value="Authorisation"/> 117 | </JTabbedPaneConstraints> 118 | </Constraint> 119 | </Constraints> 120 | </Component> 121 | <Component class="com.javierllorente.netbeans.rest.client.ui.TablePanel" name="headersPanel"> 122 | <Constraints> 123 | <Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.support.JTabbedPaneSupportLayout" value="org.netbeans.modules.form.compat2.layouts.support.JTabbedPaneSupportLayout$JTabbedPaneConstraintsDescription"> 124 | <JTabbedPaneConstraints tabName="Headers"> 125 | <Property name="tabTitle" type="java.lang.String" value="Headers"/> 126 | </JTabbedPaneConstraints> 127 | </Constraint> 128 | </Constraints> 129 | </Component> 130 | <Component class="com.javierllorente.netbeans.rest.client.ui.BodyPanel" name="bodyPanel"> 131 | <Constraints> 132 | <Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.support.JTabbedPaneSupportLayout" value="org.netbeans.modules.form.compat2.layouts.support.JTabbedPaneSupportLayout$JTabbedPaneConstraintsDescription"> 133 | <JTabbedPaneConstraints tabName="Body"> 134 | <Property name="tabTitle" type="java.lang.String" value="Body"/> 135 | </JTabbedPaneConstraints> 136 | </Constraint> 137 | </Constraints> 138 | </Component> 139 | </SubComponents> 140 | </Container> 141 | <Component class="com.javierllorente.netbeans.rest.client.ui.UrlPanel" name="urlPanel"> 142 | </Component> 143 | <Component class="javax.swing.JButton" name="jButton1"> 144 | <Properties> 145 | <Property name="text" type="java.lang.String" value="Switch to editor"/> 146 | </Properties> 147 | </Component> 148 | </SubComponents> 149 | </Container> 150 | </SubComponents> 151 | </Container> 152 | </SubComponents> 153 | </Form> 154 | -------------------------------------------------------------------------------- /src/main/java/com/javierllorente/netbeans/rest/client/http/editor/sidebar/ResponseSidebarPanel.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 Christian Lenz <christian.lenz@gmx.net>. 3 | * 4 | * Licensed to the Apache Software Foundation (ASF) under one 5 | * or more contributor license agreements. See the NOTICE file 6 | * distributed with this work for additional information 7 | * regarding copyright ownership. The ASF licenses this file 8 | * to you under the Apache License, Version 2.0 (the 9 | * "License"); you may not use this file except in compliance 10 | * with the License. You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, 15 | * software distributed under the License is distributed on an 16 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 | * KIND, either express or implied. See the License for the 18 | * specific language governing permissions and limitations 19 | * under the License. 20 | */ 21 | package com.javierllorente.netbeans.rest.client.http.editor.sidebar; 22 | 23 | import com.javierllorente.netbeans.rest.client.ui.ResponsePanel; 24 | import jakarta.ws.rs.core.MultivaluedMap; 25 | import java.awt.BorderLayout; 26 | import java.awt.Color; 27 | import java.awt.Cursor; 28 | import java.awt.Dimension; 29 | import java.awt.FlowLayout; 30 | import java.awt.Font; 31 | import java.awt.event.MouseAdapter; 32 | import java.awt.event.MouseEvent; 33 | import java.util.List; 34 | import java.util.Map; 35 | import javax.swing.BorderFactory; 36 | import javax.swing.JButton; 37 | import javax.swing.JPanel; 38 | import javax.swing.SwingUtilities; 39 | import javax.swing.text.JTextComponent; 40 | 41 | /** 42 | * Sidebar panel for displaying HTTP response content. 43 | * 44 | * @author Christian Lenz 45 | */ 46 | public class ResponseSidebarPanel extends JPanel { 47 | 48 | private static final int MIN_WIDTH = 120; 49 | private static final int MAX_WIDTH = 1200; 50 | 51 | private final ResponsePanel responsePanel; 52 | private final JTextComponent textComponent; 53 | 54 | private boolean visible; 55 | private int calculatedWidth = -1; // Cache the width once calculated 56 | private int pressXScreen; 57 | private int startWidth; 58 | 59 | public ResponseSidebarPanel(JTextComponent target) { 60 | super(new BorderLayout()); 61 | this.textComponent = target; 62 | this.visible = false; 63 | 64 | // Create header panel with close button 65 | JPanel headerPanel = new JPanel(new BorderLayout()); 66 | headerPanel.setBackground(new Color(240, 240, 240)); 67 | headerPanel.setBorder(BorderFactory.createEmptyBorder(2, 5, 2, 5)); 68 | 69 | // Title label 70 | JPanel titlePanel = new JPanel(new FlowLayout(FlowLayout.LEFT, 5, 0)); 71 | titlePanel.setBackground(new Color(240, 240, 240)); 72 | javax.swing.JLabel titleLabel = new javax.swing.JLabel("Response"); 73 | titleLabel.setFont(titleLabel.getFont().deriveFont(Font.BOLD)); 74 | titlePanel.add(titleLabel); 75 | 76 | // Close button (simple X) 77 | JButton closeButton = new JButton("\u00D7"); 78 | closeButton.setFont(closeButton.getFont().deriveFont(Font.BOLD, 16f)); 79 | closeButton.setPreferredSize(new Dimension(20, 20)); 80 | closeButton.setBorderPainted(false); 81 | closeButton.setContentAreaFilled(false); 82 | closeButton.setFocusPainted(false); 83 | closeButton.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); 84 | closeButton.setToolTipText("Close response view"); 85 | closeButton.addActionListener(e -> { 86 | setVisible(false); 87 | revalidate(); 88 | repaint(); 89 | }); 90 | 91 | headerPanel.add(titlePanel, BorderLayout.WEST); 92 | headerPanel.add(closeButton, BorderLayout.EAST); 93 | 94 | // Use the ResponsePanel component which already has all the functionality 95 | responsePanel = new ResponsePanel(); 96 | 97 | add(headerPanel, BorderLayout.NORTH); 98 | add(responsePanel, BorderLayout.CENTER); 99 | 100 | JPanel resizablePanel = createResizeHandle(); 101 | add(resizablePanel, BorderLayout.WEST); 102 | 103 | setPreferredSize(new Dimension(0, 0)); 104 | } 105 | 106 | private JPanel createResizeHandle() { 107 | JPanel handle = new JPanel(); 108 | handle.setOpaque(true); 109 | handle.setPreferredSize(new Dimension(10, 0)); 110 | handle.setCursor(Cursor.getPredefinedCursor(Cursor.W_RESIZE_CURSOR)); 111 | 112 | MouseAdapter ma = new MouseAdapter() { 113 | @Override 114 | public void mousePressed(MouseEvent e) { 115 | pressXScreen = e.getXOnScreen(); 116 | startWidth = getPreferredSize().width; 117 | } 118 | 119 | @Override 120 | public void mouseDragged(MouseEvent e) { 121 | int delta = e.getXOnScreen() - pressXScreen; 122 | calculatedWidth = clamp(startWidth - delta, MIN_WIDTH, MAX_WIDTH); 123 | setPreferredSize(new Dimension(calculatedWidth, 0)); 124 | revalidate(); 125 | 126 | if (getParent() != null) { 127 | getParent().revalidate(); 128 | } 129 | } 130 | }; 131 | 132 | handle.addMouseListener(ma); 133 | handle.addMouseMotionListener(ma); 134 | 135 | return handle; 136 | } 137 | 138 | private static int clamp(int v, int min, int max) { 139 | return Math.max(min, Math.min(max, v)); 140 | } 141 | 142 | /** 143 | * Set response headers for display. 144 | */ 145 | public void setResponseHeaders(MultivaluedMap<String, Object> headers) { 146 | if (headers != null) { 147 | for (Map.Entry<String, List<Object>> entry : headers.entrySet()) { 148 | String key = entry.getKey(); 149 | String val = entry.getValue().toString(); 150 | responsePanel.addHeader(key, val); 151 | } 152 | } 153 | } 154 | 155 | /** 156 | * Set the response content (raw). 157 | */ 158 | public void setResponse(String response) { 159 | responsePanel.setResponse(response); 160 | } 161 | 162 | @Override 163 | public void setVisible(boolean visible) { 164 | try { 165 | this.visible = visible; 166 | if (visible) { 167 | // Calculate width only once when first shown, or use cached value 168 | if (calculatedWidth == -1) { 169 | // Use invokeLater to ensure component is fully laid out 170 | SwingUtilities.invokeLater(() -> { 171 | int editorWidth = textComponent != null && textComponent.getParent() != null 172 | ? textComponent.getParent().getWidth() : 800; 173 | calculatedWidth = editorWidth / 2; // Always 50% 174 | setPreferredSize(new Dimension(calculatedWidth, 0)); 175 | setSize(new Dimension(calculatedWidth, getHeight())); 176 | if (getParent() != null) { 177 | getParent().revalidate(); 178 | getParent().repaint(); 179 | } 180 | }); 181 | } else { 182 | // Use cached width for consistent sizing 183 | setPreferredSize(new Dimension(calculatedWidth, 0)); 184 | setSize(new Dimension(calculatedWidth, getHeight())); 185 | } 186 | } else { 187 | setPreferredSize(new Dimension(0, 0)); 188 | } 189 | 190 | super.setVisible(visible); 191 | 192 | // Force parent container to relayout 193 | if (getParent() != null) { 194 | getParent().revalidate(); 195 | getParent().repaint(); 196 | } 197 | } catch (Exception e) { 198 | System.err.println("Error setting sidebar visibility: " + e.getMessage()); 199 | } 200 | } 201 | 202 | @Override 203 | public boolean isVisible() { 204 | return visible; 205 | } 206 | 207 | /** 208 | * Show the sidebar with the given response and headers. 209 | */ 210 | public void showResponse(String response, String contentType, MultivaluedMap<String, Object> headers) { 211 | try { 212 | responsePanel.clear(); 213 | responsePanel.setContentType(contentType); 214 | setResponse(response); 215 | setResponseHeaders(headers); 216 | responsePanel.showResponse(); 217 | setVisible(true); 218 | revalidate(); 219 | repaint(); 220 | } catch (Exception e) { 221 | System.err.println("Error showing response: " + e.getMessage()); 222 | e.printStackTrace(); 223 | } 224 | } 225 | } 226 | -------------------------------------------------------------------------------- /src/main/java/com/javierllorente/netbeans/rest/client/ui/ResponsePanel.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022-2025 Javier Llorente <javier@opensuse.org>. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.javierllorente.netbeans.rest.client.ui; 17 | 18 | import com.javierllorente.netbeans.rest.util.FormatUtils; 19 | import com.javierllorente.netbeans.rest.client.editor.RestMediaType; 20 | import jakarta.json.Json; 21 | import jakarta.json.JsonReader; 22 | import jakarta.ws.rs.core.MediaType; 23 | import java.awt.BorderLayout; 24 | import java.awt.Desktop; 25 | import java.awt.Dimension; 26 | import java.awt.FlowLayout; 27 | import java.io.File; 28 | import java.io.IOException; 29 | import java.io.StringReader; 30 | import java.nio.file.Files; 31 | import javax.swing.ButtonGroup; 32 | import javax.swing.JButton; 33 | import javax.swing.JComponent; 34 | import javax.swing.JEditorPane; 35 | import javax.swing.JLayer; 36 | import javax.swing.JOptionPane; 37 | import javax.swing.JPanel; 38 | import javax.swing.JScrollPane; 39 | import javax.swing.JTabbedPane; 40 | import javax.swing.JToggleButton; 41 | import org.openide.text.CloneableEditorSupport; 42 | 43 | /** 44 | * 45 | * @author Javier Llorente <javier@opensuse.org> 46 | */ 47 | public class ResponsePanel extends JPanel { 48 | 49 | private final JTabbedPane responseTabbedPane; 50 | private final JEditorPane responseEditorPane; 51 | private final JToggleButton prettyButton; 52 | private final TablePanel responseHeadersTable; 53 | private final StatusLabel statusLabel; 54 | private final LineNumberComponent lineNumberComponent; 55 | private final JButton previewButton; 56 | private String mimePath = MediaType.TEXT_PLAIN; 57 | private String response; 58 | 59 | public ResponsePanel() { 60 | super(new BorderLayout()); 61 | 62 | responseTabbedPane = new JTabbedPane(); 63 | responseTabbedPane.setPreferredSize(new Dimension(705, 150)); 64 | 65 | responseEditorPane = new JEditorPane(); 66 | responseEditorPane.setEditable(false); 67 | responseEditorPane.setCursor(new java.awt.Cursor(java.awt.Cursor.TEXT_CURSOR)); 68 | responseEditorPane.setAutoscrolls(true); 69 | JScrollPane responseScrollPane = new JScrollPane(); 70 | responseScrollPane.setViewportView(responseEditorPane); 71 | 72 | JPanel topPanel = new JPanel(); 73 | topPanel.setLayout(new FlowLayout(FlowLayout.LEFT)); 74 | 75 | prettyButton = new JToggleButton("Pretty", true); 76 | JToggleButton rawButton = new JToggleButton("Raw", false); 77 | prettyButton.addItemListener((ie) -> { 78 | showResponse(); 79 | }); 80 | 81 | ButtonGroup formatGroup = new ButtonGroup(); 82 | formatGroup.add(prettyButton); 83 | formatGroup.add(rawButton); 84 | topPanel.add(prettyButton); 85 | topPanel.add(rawButton); 86 | 87 | previewButton = new JButton("Preview"); 88 | previewButton.setToolTipText("Open HTML response in browser"); 89 | previewButton.setVisible(false); // Initially hidden, shown only for HTML responses 90 | previewButton.addActionListener((ae) -> { 91 | renderHtml(); 92 | }); 93 | topPanel.add(previewButton); 94 | 95 | responseScrollPane = new JScrollPane(); 96 | 97 | JPanel responseBodyPanel = new JPanel(); 98 | responseBodyPanel.setLayout(new BorderLayout()); 99 | responseBodyPanel.add(topPanel, BorderLayout.NORTH); 100 | responseBodyPanel.add(responseScrollPane, BorderLayout.CENTER); 101 | 102 | responseEditorPane.setEditorKit(CloneableEditorSupport.getEditorKit(mimePath)); 103 | responseScrollPane.setViewportView(responseEditorPane); 104 | 105 | responseTabbedPane.addTab("Body", responseBodyPanel); 106 | responseHeadersTable = new TablePanel(); 107 | responseHeadersTable.setReadOnly(); 108 | responseTabbedPane.addTab("Headers", responseHeadersTable); 109 | 110 | statusLabel = new StatusLabel(); 111 | JLayer<JComponent> decoratedPane = new JLayer<>(responseTabbedPane, statusLabel); 112 | add(decoratedPane); 113 | 114 | lineNumberComponent = new LineNumberComponent(responseEditorPane); 115 | 116 | responseScrollPane.getVerticalScrollBar().addAdjustmentListener(e -> lineNumberComponent.repaint()); 117 | responseScrollPane.getHorizontalScrollBar().addAdjustmentListener(e -> lineNumberComponent.repaint()); 118 | responseScrollPane.setRowHeaderView(lineNumberComponent); 119 | 120 | } 121 | 122 | public void setContentType(String contentType) { 123 | mimePath = MediaType.TEXT_PLAIN; 124 | if (contentType.startsWith(MediaType.APPLICATION_XML)) { 125 | mimePath = RestMediaType.XML; 126 | } else if (contentType.startsWith(MediaType.APPLICATION_JSON) 127 | || contentType.startsWith("application/javascript")) { 128 | mimePath = RestMediaType.JSON; 129 | } else if (contentType.startsWith(MediaType.TEXT_HTML)) { 130 | mimePath = MediaType.TEXT_HTML; 131 | } 132 | responseEditorPane.setEditorKit(CloneableEditorSupport.getEditorKit(mimePath)); 133 | 134 | // Show Preview button only for HTML responses 135 | previewButton.setVisible(mimePath.equals(MediaType.TEXT_HTML)); 136 | } 137 | 138 | public void setResponse(String response) { 139 | this.response = response; 140 | } 141 | 142 | private String formatResponse() { 143 | String prettyOrNotResponse = response; 144 | if (prettyButton.isSelected()) { 145 | switch (mimePath) { 146 | case RestMediaType.JSON: 147 | try (JsonReader jsonReader = Json.createReader(new StringReader(prettyOrNotResponse))) { 148 | prettyOrNotResponse = FormatUtils.jsonPrettyFormat(jsonReader.read()); 149 | } 150 | break; 151 | case RestMediaType.XML: 152 | prettyOrNotResponse = FormatUtils.xmlPrettyFormat(prettyOrNotResponse); 153 | break; 154 | case MediaType.TEXT_HTML: 155 | prettyOrNotResponse = FormatUtils.htmlPrettyFormat(prettyOrNotResponse); 156 | break; 157 | } 158 | } 159 | 160 | lineNumberComponent.repaint(); 161 | 162 | return prettyOrNotResponse; 163 | } 164 | 165 | public void showResponse() { 166 | String prettyOrNotResponse = formatResponse(); 167 | responseEditorPane.setText(prettyOrNotResponse); 168 | responseEditorPane.setCaretPosition(0); 169 | } 170 | 171 | /** 172 | * Render HTML response in system browser. 173 | */ 174 | private void renderHtml() { 175 | if (response == null || response.isEmpty()) { 176 | JOptionPane.showMessageDialog(this, "No HTML content to render", "Info", JOptionPane.INFORMATION_MESSAGE); 177 | return; 178 | } 179 | 180 | try { 181 | // Create temporary HTML file 182 | File tempFile = File.createTempFile("rest-response-", ".html"); 183 | tempFile.deleteOnExit(); 184 | Files.writeString(tempFile.toPath(), response); 185 | 186 | // Open in default browser 187 | if (Desktop.isDesktopSupported()) { 188 | Desktop.getDesktop().browse(tempFile.toURI()); 189 | } else { 190 | JOptionPane.showMessageDialog(this, "Desktop not supported - cannot open browser", "Error", JOptionPane.ERROR_MESSAGE); 191 | } 192 | } catch (IOException ex) { 193 | JOptionPane.showMessageDialog(this, "Failed to render HTML: " + ex.getMessage(), "Error", JOptionPane.ERROR_MESSAGE); 194 | } 195 | } 196 | 197 | public void clearResponse() { 198 | responseEditorPane.setText(""); 199 | responseEditorPane.setCaretPosition(0); 200 | } 201 | 202 | public void addHeader(String key, String value) { 203 | responseHeadersTable.addRow(key, value); 204 | } 205 | 206 | public void clearHeadersTable() { 207 | responseHeadersTable.removeAllRows(); 208 | } 209 | 210 | public void setStatus(String status) { 211 | statusLabel.setText(status); 212 | revalidate(); 213 | repaint(); 214 | } 215 | 216 | public void clearStatus() { 217 | statusLabel.setText(""); 218 | revalidate(); 219 | repaint(); 220 | } 221 | 222 | public void clear() { 223 | previewButton.setVisible(false); 224 | clearResponse(); 225 | clearStatus(); 226 | clearHeadersTable(); 227 | } 228 | 229 | } 230 | -------------------------------------------------------------------------------- /src/main/java/com/javierllorente/netbeans/rest/client/http/editor/sidebar/request/RequestProcessor.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 Christian Lenz <christian.lenz@gmx.net>. 3 | * 4 | * Licensed to the Apache Software Foundation (ASF) under one 5 | * or more contributor license agreements. See the NOTICE file 6 | * distributed with this work for additional information 7 | * regarding copyright ownership. The ASF licenses this file 8 | * to you under the Apache License, Version 2.0 (the 9 | * "License"); you may not use this file except in compliance 10 | * with the License. You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, 15 | * software distributed under the License is distributed on an 16 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 | * KIND, either express or implied. See the License for the 18 | * specific language governing permissions and limitations 19 | * under the License. 20 | */ 21 | package com.javierllorente.netbeans.rest.client.http.editor.sidebar.request; 22 | 23 | import com.javierllorente.netbeans.rest.client.RestClient; 24 | import com.javierllorente.netbeans.rest.client.http.editor.sidebar.ResponseSidebarManager; 25 | import com.javierllorente.netbeans.rest.client.http.editor.syntax.antlr.HTTPLexer; 26 | import com.javierllorente.netbeans.rest.client.http.editor.syntax.antlr.HTTPParser; 27 | import com.javierllorente.netbeans.rest.client.ui.RestClientTopComponent; 28 | import jakarta.ws.rs.ProcessingException; 29 | import jakarta.ws.rs.core.MultivaluedHashMap; 30 | import jakarta.ws.rs.core.MultivaluedMap; 31 | import java.util.ArrayList; 32 | import java.util.List; 33 | import javax.swing.text.JTextComponent; 34 | import javax.swing.text.StyledDocument; 35 | import org.antlr.v4.runtime.CharStreams; 36 | import org.antlr.v4.runtime.CommonTokenStream; 37 | import org.openide.text.NbDocument; 38 | 39 | /** 40 | * 41 | * @author Christian Lenz 42 | */ 43 | public class RequestProcessor implements IRequestProcessor { 44 | 45 | public final List<Request> currentRequests; 46 | private final StyledDocument document; 47 | private final RestClient restClient; 48 | private JTextComponent textComponent; 49 | private RestClientTopComponent restClientUi; 50 | 51 | public RequestProcessor(StyledDocument document) { 52 | this.currentRequests = new ArrayList<>(); 53 | this.document = document; 54 | 55 | this.restClient = new RestClient(); 56 | } 57 | 58 | /** 59 | * Set the text component for this processor. This is used to show responses 60 | * in the sidebar. 61 | */ 62 | public void setTextComponent(JTextComponent textComponent) { 63 | this.textComponent = textComponent; 64 | } 65 | 66 | @Override 67 | public List<Request> getRequests() { 68 | return currentRequests; 69 | } 70 | 71 | @Override 72 | public void updateRequestsList(String text) { 73 | currentRequests.clear(); 74 | 75 | HTTPLexer lexer = new HTTPLexer(CharStreams.fromString(text)); 76 | CommonTokenStream tokens = new CommonTokenStream(lexer); 77 | HTTPParser parser = new HTTPParser(tokens); 78 | 79 | HTTPParser.HttpRequestsFileContext fileCtx = parser.httpRequestsFile(); 80 | 81 | if (fileCtx == null) { 82 | return; 83 | } 84 | 85 | List<HTTPParser.RequestBlockContext> requestBlock = fileCtx.requestBlock(); 86 | 87 | for (HTTPParser.RequestBlockContext requestBlockContext : requestBlock) { 88 | HTTPParser.RequestContext request = requestBlockContext.request(); 89 | 90 | if (request == null) { 91 | return; 92 | } 93 | 94 | HTTPParser.RequestLineContext requestLine = request.requestLine(); 95 | 96 | if (requestLine == null) { 97 | return; 98 | } 99 | 100 | currentRequests.add(new Request(request, requestLine.getText(), NbDocument.findLineNumber(document, requestLine.start.getStartIndex()))); 101 | } 102 | } 103 | 104 | @Override 105 | public void callRequest(HTTPParser.RequestContext requestContext) { 106 | if (requestContext == null) { 107 | return; 108 | } 109 | 110 | HTTPParser.RequestLineContext requestLine = requestContext.requestLine(); 111 | 112 | if (requestLine == null) { 113 | return; 114 | } 115 | 116 | HTTPParser.RequestTargetContext requestTarget = requestLine.requestTarget(); 117 | 118 | if (requestTarget == null) { 119 | return; 120 | } 121 | 122 | this.restClient.setHeaders(getHeaders(requestContext.requestHeaders())); 123 | this.restClient.setBody(getBody(requestContext.requestBodySection())); 124 | 125 | try { 126 | String response = this.restClient.request(requestTarget.getText(), requestLine.METHOD() == null ? "GET" : requestLine.METHOD().getText()); 127 | 128 | // Show response in sidebar if textComponent is available 129 | if (textComponent != null && response != null) { 130 | String contentType = ""; 131 | var responseHeaders = this.restClient.getResponseHeaders(); 132 | 133 | if (responseHeaders != null && responseHeaders.containsKey("content-type") 134 | && !responseHeaders.get("content-type").isEmpty()) { 135 | contentType = (String) responseHeaders.get("content-type").get(0); 136 | } 137 | 138 | // Pass headers to sidebar 139 | ResponseSidebarManager.getInstance().showResponse(textComponent, response, contentType, responseHeaders); 140 | } 141 | } catch (ProcessingException ex) { 142 | ResponseSidebarManager.getInstance().showResponse(textComponent, ex.getMessage(), "", null); 143 | } 144 | } 145 | 146 | @Override 147 | public void openRequestInUi(HTTPParser.RequestContext requestContext) { 148 | if (requestContext == null) { 149 | return; 150 | } 151 | 152 | HTTPParser.RequestLineContext requestLine = requestContext.requestLine(); 153 | 154 | if (requestLine == null) { 155 | return; 156 | } 157 | 158 | HTTPParser.RequestTargetContext requestTarget = requestLine.requestTarget(); 159 | 160 | if (requestTarget == null) { 161 | return; 162 | } 163 | 164 | String method = "GET"; // Default 165 | if (requestLine.METHOD() != null) { 166 | method = requestLine.METHOD().getText(); 167 | } 168 | 169 | this.restClient.setMethod(method); 170 | this.restClient.setUri(requestTarget.getText()); 171 | this.restClient.setHeaders(getHeaders(requestContext.requestHeaders())); 172 | 173 | String body = getBody(requestContext.requestBodySection()); 174 | 175 | if (body != null) { 176 | restClient.setBodyType("Text"); 177 | this.restClient.setBody(body); 178 | } 179 | 180 | restClientUi = new RestClientTopComponent(this.restClient); 181 | restClientUi.open(); 182 | restClientUi.requestActive(); 183 | } 184 | 185 | private MultivaluedMap<String, String> getHeaders(HTTPParser.RequestHeadersContext requestHeadersContext) { 186 | MultivaluedMap<String, String> headers = new MultivaluedHashMap<>(); 187 | 188 | if (requestHeadersContext == null) { 189 | return headers; 190 | } 191 | 192 | List<HTTPParser.HeaderLineContext> headerLine = requestHeadersContext.headerLine(); 193 | 194 | for (HTTPParser.HeaderLineContext headerLineContext : headerLine) { 195 | HTTPParser.HeaderContext header = headerLineContext.header(); 196 | 197 | if (header == null) { 198 | break; 199 | } 200 | 201 | HTTPParser.HeaderFieldContext headerField = header.headerField(); 202 | 203 | if (headerField == null) { 204 | break; 205 | } 206 | 207 | HTTPParser.HeaderFieldNameContext headerFieldNameContext = headerField.headerFieldName(); 208 | HTTPParser.HeaderFieldValueContext headerFieldValueContext = headerField.headerFieldValue(); 209 | 210 | if (headerFieldNameContext == null || headerFieldValueContext == null) { 211 | break; 212 | } 213 | 214 | String key = headerFieldNameContext.getText(); 215 | String value = headerFieldValueContext.getText(); 216 | 217 | if (!headers.containsKey(key) || !headers.get(key).contains(value)) { 218 | headers.add(key, value); 219 | } 220 | } 221 | 222 | return headers; 223 | } 224 | 225 | private String getBody(HTTPParser.RequestBodySectionContext requestBodySectionContext) { 226 | if (requestBodySectionContext == null) { 227 | return ""; 228 | } 229 | 230 | HTTPParser.RequestBodyContext requestBody = requestBodySectionContext.requestBody(); 231 | 232 | if (requestBody == null) { 233 | return ""; 234 | } 235 | 236 | if (requestBody.bodyWithStarter() != null) { 237 | HTTPParser.BodyContentContext bodyContent = requestBody.bodyWithStarter().bodyContent(); 238 | return bodyContent != null ? bodyContent.getText() : ""; 239 | } else if (requestBody.directBodyContent() != null) { 240 | return requestBody.directBodyContent().getText(); 241 | } 242 | 243 | return ""; 244 | } 245 | } 246 | --------------------------------------------------------------------------------