@@ -3,9 +3,12 @@ import { URL } from "url";
33import * as vscode from "vscode" ;
44
55import { ContextExpressionClient } from "../sourcecontrol/clients/contextExpressionClient" ;
6+ import { GlobalDocumentationResponse , ResolveContextExpressionResponse } from "../core/types" ;
67import { handleError } from "../../utils" ;
78
89const sharedClient = new ContextExpressionClient ( ) ;
10+ const CONTEXT_HELP_PANEL_VIEW_TYPE = "contextHelpPreview" ;
11+ const CONTEXT_HELP_TITLE = "Ajuda de Contexto" ;
912
1013export async function resolveContextExpression ( ) : Promise < void > {
1114 const editor = vscode . window . activeTextEditor ;
@@ -28,45 +31,69 @@ export async function resolveContextExpression(): Promise<void> {
2831 const response = await sharedClient . resolve ( document , { routine, contextExpression } ) ;
2932 const data = response ?? { } ;
3033
31- if ( typeof data . status === "string" && data . status . toLowerCase ( ) === "success" && data . textExpression ) {
32- const eol = document . eol === vscode . EndOfLine . CRLF ? "\r\n" : "\n" ;
34+ if ( typeof data === "string" ) {
35+ await handleContextHelpDocumentationContent ( data ) ;
36+ return ;
37+ }
38+
39+ if ( hasGlobalDocumentationContent ( data ) ) {
40+ const normalizedContent = normalizeGlobalDocumentationContent ( data . content ) ;
41+
42+ if ( normalizedContent . trim ( ) ) {
43+ await handleContextHelpDocumentationContent ( normalizedContent ) ;
44+ } else {
45+ const message = data . message || "A ajuda de contexto não retornou nenhum conteúdo." ;
46+ void vscode . window . showInformationMessage ( message ) ;
47+ }
48+ return ;
49+ }
50+
51+ if ( isSuccessfulTextExpression ( data ) ) {
52+ const hasGifCommand = / - - g i f \b / i. test ( contextExpression ) ;
3353 let normalizedTextExpression = data . textExpression . replace ( / \r ? \n / g, "\n" ) ;
3454 let gifUri : vscode . Uri | undefined ;
3555
36- if ( / - - g i f \b / i . test ( contextExpression ) ) {
56+ if ( hasGifCommand ) {
3757 const extracted = extractGifUri ( normalizedTextExpression ) ;
3858 normalizedTextExpression = extracted . textWithoutGifUri ;
3959 gifUri = extracted . gifUri ;
4060 }
4161
42- const textExpression = normalizedTextExpression . replace ( / \r ? \n / g, eol ) ;
43- const formattedTextExpression = textExpression ;
62+ if ( ! hasGifCommand ) {
63+ const eol = document . eol === vscode . EndOfLine . CRLF ? "\r\n" : "\n" ;
64+ const textExpression = normalizedTextExpression . replace ( / \r ? \n / g, eol ) ;
65+ const formattedTextExpression = textExpression ;
66+
67+ let rangeToReplace : vscode . Range ;
68+ if ( selection . isEmpty ) {
69+ const fallbackLine = document . lineAt ( selection . active . line ) ;
70+ rangeToReplace = fallbackLine . range ;
71+ } else {
72+ const start = document . lineAt ( selection . start . line ) . range . start ;
73+ const replacementEnd = contextInfo . replacementEnd ?? document . lineAt ( selection . end . line ) . range . end ;
74+ rangeToReplace = new vscode . Range ( start , replacementEnd ) ;
75+ }
4476
45- let rangeToReplace : vscode . Range ;
46- if ( selection . isEmpty ) {
47- const fallbackLine = document . lineAt ( selection . active . line ) ;
48- rangeToReplace = fallbackLine . range ;
49- } else {
50- const start = document . lineAt ( selection . start . line ) . range . start ;
51- const replacementEnd = contextInfo . replacementEnd ?? document . lineAt ( selection . end . line ) . range . end ;
52- rangeToReplace = new vscode . Range ( start , replacementEnd ) ;
77+ await editor . edit ( ( editBuilder ) => {
78+ editBuilder . replace ( rangeToReplace , formattedTextExpression ) ;
79+ } ) ;
5380 }
5481
55- await editor . edit ( ( editBuilder ) => {
56- editBuilder . replace ( rangeToReplace , formattedTextExpression ) ;
57- } ) ;
58-
5982 if ( gifUri ) {
6083 try {
6184 await showGifInWebview ( gifUri ) ;
6285 } catch ( error ) {
6386 handleError ( error , "Failed to open GIF from context expression." ) ;
6487 }
6588 }
66- } else {
67- const errorMessage = data . message || "Failed to resolve context expression." ;
68- void vscode . window . showErrorMessage ( errorMessage ) ;
89+ return ;
6990 }
91+
92+ const errorMessage =
93+ typeof data === "object" && data && "message" in data && typeof data . message === "string"
94+ ? data . message
95+ : "Failed to resolve context expression." ;
96+ void vscode . window . showErrorMessage ( errorMessage ) ;
7097 } catch ( error ) {
7198 handleError ( error , "Failed to resolve context expression." ) ;
7299 }
@@ -132,6 +159,156 @@ function extractGifUri(text: string): {
132159 return { textWithoutGifUri : processedLines . join ( "\n" ) , gifUri } ;
133160}
134161
162+ async function handleContextHelpDocumentationContent ( rawContent : string ) : Promise < void > {
163+ const sanitizedContent = sanitizeContextHelpContent ( rawContent ) ;
164+
165+ if ( ! sanitizedContent . trim ( ) ) {
166+ void vscode . window . showInformationMessage ( "A ajuda de contexto não retornou nenhum conteúdo." ) ;
167+ return ;
168+ }
169+
170+ const errorMessage = extractContextHelpError ( sanitizedContent ) ;
171+ if ( errorMessage ) {
172+ void vscode . window . showErrorMessage ( errorMessage ) ;
173+ return ;
174+ }
175+
176+ await showContextHelpPreview ( sanitizedContent ) ;
177+ }
178+
179+ function sanitizeContextHelpContent ( content : string ) : string {
180+ let sanitized = content . replace ( / \{ " s t a t u s " : " s u c c e s s " , " t e x t E x p r e s s i o n " : " " \} \s * $ / i, "" ) ;
181+
182+ sanitized = sanitized . replace ( / ^ \s * = + \s * G l o b a l D o c u m e n t a t i o n \s * = + \s * (?: \r ? \n ) ? / i, "" ) ;
183+
184+ return sanitized . replace ( / \r ? \n / g, "\n" ) ;
185+ }
186+
187+ function extractContextHelpError ( content : string ) : string | undefined {
188+ const commandNotImplemented = content . match ( / C o m a n d o \s + " ( [ ^ " ] + ) " \s + n [ ã a ] o i m p l e m e n t a d o ! / i) ;
189+ if ( commandNotImplemented ) {
190+ return commandNotImplemented [ 0 ] . replace ( / \s + / g, " " ) ;
191+ }
192+
193+ return undefined ;
194+ }
195+
196+ async function showContextHelpPreview ( content : string ) : Promise < void > {
197+ const panel = vscode . window . createWebviewPanel (
198+ CONTEXT_HELP_PANEL_VIEW_TYPE ,
199+ CONTEXT_HELP_TITLE ,
200+ { viewColumn : vscode . ViewColumn . Beside , preserveFocus : false } ,
201+ {
202+ enableFindWidget : true ,
203+ enableScripts : false ,
204+ retainContextWhenHidden : false ,
205+ }
206+ ) ;
207+
208+ panel . webview . html = getContextHelpWebviewHtml ( panel . webview , content ) ;
209+ }
210+
211+ function getContextHelpWebviewHtml ( webview : vscode . Webview , content : string ) : string {
212+ const escapedContent = escapeHtml ( content ) ;
213+ const cspSource = escapeHtml ( webview . cspSource ) ;
214+ const escapedTitle = escapeHtml ( CONTEXT_HELP_TITLE ) ;
215+
216+ return `<!DOCTYPE html>
217+ <html lang="pt-BR">
218+ <head>
219+ <meta charset="UTF-8" />
220+ <meta http-equiv="Content-Security-Policy" content="default-src 'none'; style-src ${ cspSource } 'unsafe-inline';" />
221+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
222+ <title>${ escapedTitle } </title>
223+ <style>
224+ body {
225+ margin: 0;
226+ padding: 16px;
227+ background-color: var(--vscode-editor-background, #1e1e1e);
228+ color: var(--vscode-editor-foreground, #d4d4d4);
229+ font-family: var(--vscode-editor-font-family, Consolas, 'Courier New', monospace);
230+ font-size: var(--vscode-editor-font-size, 14px);
231+ line-height: 1.5;
232+ }
233+
234+ pre {
235+ white-space: pre; /* em vez de pre-wrap */
236+ word-break: normal; /* em vez de break-word */
237+ overflow-x: auto; /* barra horizontal quando precisar */
238+ overflow-y: auto; /* mantém a vertical também */
239+ max-width: 100%;
240+ }
241+ </style>
242+
243+ </head>
244+ <body>
245+ <pre>${ escapedContent } </pre>
246+ </body>
247+ </html>` ;
248+ }
249+
250+ function hasGlobalDocumentationContent (
251+ value : unknown
252+ ) : value is Pick < GlobalDocumentationResponse , "content" | "message" > {
253+ if ( ! isRecord ( value ) ) {
254+ return false ;
255+ }
256+
257+ if ( ! ( "content" in value ) ) {
258+ return false ;
259+ }
260+
261+ const content = ( value as GlobalDocumentationResponse ) . content ;
262+
263+ return (
264+ typeof content === "string" ||
265+ Array . isArray ( content ) ||
266+ ( content !== null && typeof content === "object" ) ||
267+ content === null
268+ ) ;
269+ }
270+
271+ function normalizeGlobalDocumentationContent ( content : GlobalDocumentationResponse [ "content" ] ) : string {
272+ if ( typeof content === "string" ) {
273+ return content ;
274+ }
275+
276+ if ( Array . isArray ( content ) ) {
277+ return content . join ( "\n" ) ;
278+ }
279+
280+ if ( content && typeof content === "object" ) {
281+ try {
282+ return JSON . stringify ( content , null , 2 ) ;
283+ } catch ( error ) {
284+ handleError ( error , "Failed to parse global documentation content." ) ;
285+ }
286+ }
287+
288+ return "" ;
289+ }
290+
291+ function isSuccessfulTextExpression (
292+ value : unknown
293+ ) : value is Required < Pick < ResolveContextExpressionResponse , "textExpression" > > & ResolveContextExpressionResponse {
294+ if ( ! isRecord ( value ) ) {
295+ return false ;
296+ }
297+
298+ const { status, textExpression } = value as ResolveContextExpressionResponse ;
299+
300+ return (
301+ typeof status === "string" &&
302+ status . toLowerCase ( ) === "success" &&
303+ typeof textExpression === "string" &&
304+ textExpression . length > 0
305+ ) ;
306+ }
307+
308+ function isRecord ( value : unknown ) : value is Record < string , unknown > {
309+ return typeof value === "object" && value !== null ;
310+ }
311+
135312function getFileUriFromText ( text : string ) : vscode . Uri | undefined {
136313 const trimmed = text . trim ( ) ;
137314 if ( ! trimmed . toLowerCase ( ) . startsWith ( "file://" ) ) {
0 commit comments