Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletions fontes/extensao-web.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ import {

// Importações individuais dos formatadores
import { DeleguaProvedorFormatacao } from './formatadores/delegua-provedor-formatacao';
import { ProvedorTarefasLiquidoWeb } from './tarefas/provedor-tarefas-web';
import { comandosLiquido, TIPO_TAREFA_LIQUIDO } from './tarefas/comandos-liquido';
import { VisualgProvedorFormatacao } from './formatadores/visualg-provedor-formatacao';
import { MaplerProvedorFormatacao } from './formatadores/mapler-provedor-formatacao';
import { PotigolProvedorFormatacao } from './formatadores/potigol-provedor-formatacao';
Expand Down Expand Up @@ -437,6 +439,40 @@ export function activate(context: vscode.ExtensionContext) {
);
context.subscriptions.push(abrirPainelEntradaESaida);

// Tarefas do Liquido no ambiente Web, executadas via CustomExecution
// (o host Web não tem shell). O servidor de desenvolvimento é omitido aqui.
context.subscriptions.push(
vscode.tasks.registerTaskProvider(
TIPO_TAREFA_LIQUIDO,
new ProvedorTarefasLiquidoWeb()
)
);

for (const comando of comandosLiquido) {
// No Web, não registra o comando do servidor (indisponível).
if (comando.requerServidor) {
continue;
}
context.subscriptions.push(
vscode.commands.registerCommand(
`extension.designliquido.liquido.${comando.id}`,
async () => {
const tarefas = await vscode.tasks.fetchTasks({ type: TIPO_TAREFA_LIQUIDO });
const tarefa = tarefas.find(
t => (t.definition as any).comando === comando.id
);
if (tarefa) {
await vscode.tasks.executeTask(tarefa);
} else {
vscode.window.showErrorMessage(
`Não foi possível localizar a tarefa Liquido "${comando.id}".`
);
}
}
)
);
}

// Configurar depuração para Web
configurarDepuracao(
context,
Expand Down
34 changes: 34 additions & 0 deletions fontes/extensao.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import * as fs from 'fs';
import * as path from 'path';

import { configurarDepuracao } from './depuracao/configuracao-depuracao';
import { ProvedorTarefasLiquidoDesktop } from './tarefas/provedor-tarefas-desktop';
import { comandosLiquido, TIPO_TAREFA_LIQUIDO } from './tarefas/comandos-liquido';
import { FabricaAdaptadorDepuracaoEmbutido } from './depuracao/fabricas';
import {
DeleguaAdapterServerDescriptorFactory,
Expand Down Expand Up @@ -639,6 +641,38 @@ export function activate(context: vscode.ExtensionContext) {
);
context.subscriptions.push(abrirPainelEntradaESaida);

// Tarefas do Liquido (servidor, novo, gerar, documentar, banco, testes),
// executadas via ShellExecution no desktop.
context.subscriptions.push(
vscode.tasks.registerTaskProvider(
TIPO_TAREFA_LIQUIDO,
new ProvedorTarefasLiquidoDesktop()
)
);

// Comandos de conveniência: cada um dispara a tarefa Liquido correspondente,
// permitindo botões (ex.: na barra de título do editor) e atalhos.
for (const comando of comandosLiquido) {
context.subscriptions.push(
vscode.commands.registerCommand(
`extension.designliquido.liquido.${comando.id}`,
async () => {
const tarefas = await vscode.tasks.fetchTasks({ type: TIPO_TAREFA_LIQUIDO });
const tarefa = tarefas.find(
t => (t.definition as any).comando === comando.id
);
if (tarefa) {
await vscode.tasks.executeTask(tarefa);
} else {
vscode.window.showErrorMessage(
`Não foi possível localizar a tarefa Liquido "${comando.id}".`
);
}
}
)
);
}


// adaptadores de depuração podem ser executados de diferentes formas usando um vscode.DebugAdapterDescriptorFactory:
switch (runMode) {
Expand Down
74 changes: 74 additions & 0 deletions fontes/tarefas/comandos-liquido.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
/**
* Catálogo dos comandos do CLI `liquido`, compartilhado pelos provedores de
* tarefa do desktop e da Web.
*
* Cada comando descreve como é executado:
* - `argumentosCli`: os argumentos passados ao binário `liquido` (usados pelo
* provedor desktop, via ShellExecution);
* - `disponivelNaWeb`: se o comando pode rodar no host de extensão Web, onde
* não há shell nem `child_process` (usado pelo provedor Web, via
* CustomExecution);
* - `requerServidor`: marca o comando que sobe um servidor HTTP de longa
* duração, cujo suporte na Web é experimental (Simple Browser).
*/
export interface ComandoLiquido {
id: string;
titulo: string;
descricao: string;
argumentosCli: string[];
disponivelNaWeb: boolean;
requerServidor?: boolean;
}

export const comandosLiquido: ComandoLiquido[] = [
{
id: 'servidor',
titulo: 'Liquido: Iniciar servidor de desenvolvimento',
descricao: 'Sobe o servidor de desenvolvimento do projeto Liquido.',
argumentosCli: [],
disponivelNaWeb: true,
requerServidor: true
},
{
id: 'novo',
titulo: 'Liquido: Novo projeto',
descricao: 'Cria um novo projeto Liquido no diretório atual.',
argumentosCli: ['novo'],
disponivelNaWeb: true
},
{
id: 'gerar',
titulo: 'Liquido: Gerar código',
descricao: 'Gera código a partir dos modelos do projeto.',
argumentosCli: ['gerar'],
disponivelNaWeb: true
},
{
id: 'documentar',
titulo: 'Liquido: Gerar documentação OpenAPI',
descricao: 'Lê as rotas do projeto e gera a documentação OpenAPI.',
argumentosCli: ['documentar'],
disponivelNaWeb: true
},
{
id: 'banco-iniciar',
titulo: 'Liquido: Inicializar banco de dados',
descricao: 'Inicializa a estrutura e os dados do banco de dados do projeto.',
argumentosCli: ['banco', 'iniciar'],
disponivelNaWeb: true
},
{
id: 'testes',
titulo: 'Liquido: Rodar testes',
descricao: 'Executa a suíte de testes em Delégua do projeto.',
argumentosCli: ['testes'],
disponivelNaWeb: true
}
];

/** Tipo da definição de tarefa contribuída no package.json. */
export const TIPO_TAREFA_LIQUIDO = 'liquido';

export function comandoPorId(id: string): ComandoLiquido | undefined {
return comandosLiquido.find(comando => comando.id === id);
}
75 changes: 75 additions & 0 deletions fontes/tarefas/provedor-tarefas-desktop.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import * as vscode from 'vscode';

import { comandosLiquido, comandoPorId, TIPO_TAREFA_LIQUIDO, ComandoLiquido } from './comandos-liquido';

/**
* Definição de tarefa Liquido para o desktop.
*/
interface DefinicaoTarefaLiquido extends vscode.TaskDefinition {
type: typeof TIPO_TAREFA_LIQUIDO;
comando: string;
}

/**
* Provedor de tarefas para o host de extensão de desktop.
*
* Cada comando do CLI `liquido` vira uma `vscode.Task` executada via
* `ShellExecution`, chamando o binário `liquido` local do projeto
* (`node_modules/.bin/liquido`). Assim o usuário roda servidor, geração,
* documentação, banco e testes pela paleta de tarefas, sem alternar para um
* terminal externo nem decorar comandos.
*/
export class ProvedorTarefasLiquidoDesktop implements vscode.TaskProvider {
static readonly tipo = TIPO_TAREFA_LIQUIDO;

provideTasks(): vscode.Task[] {
return comandosLiquido.map(comando => this.criarTarefa(comando));
}

resolveTask(tarefa: vscode.Task): vscode.Task | undefined {
const comando = comandoPorId((tarefa.definition as DefinicaoTarefaLiquido).comando);
if (!comando) {
return undefined;
}
// Preserva a definição informada pelo usuário (em tasks.json) ao
// reconstruir a execução.
return this.criarTarefa(comando, tarefa.definition as DefinicaoTarefaLiquido);
}

private criarTarefa(
comando: ComandoLiquido,
definicao?: DefinicaoTarefaLiquido
): vscode.Task {
const definicaoTarefa: DefinicaoTarefaLiquido = definicao ?? {
type: TIPO_TAREFA_LIQUIDO,
comando: comando.id
};

// Caminho para o binário local do projeto, entre aspas para tolerar
// espaços no caminho da pasta de trabalho.
const binarioLiquido = '"${workspaceFolder}/node_modules/.bin/liquido"';
const execucao = new vscode.ShellExecution(binarioLiquido, comando.argumentosCli);

const tarefa = new vscode.Task(
definicaoTarefa,
vscode.TaskScope.Workspace,
comando.titulo.replace(/^Liquido:\s*/, ''),
'liquido',
execucao
);

// O servidor é um processo de longa duração: mantém o painel dedicado e
// não some ao terminar. Os demais são de execução curta.
tarefa.isBackground = comando.requerServidor === true;
tarefa.presentationOptions = {
reveal: vscode.TaskRevealKind.Always,
panel: comando.requerServidor
? vscode.TaskPanelKind.Dedicated
: vscode.TaskPanelKind.Shared,
clear: true
};
tarefa.detail = comando.descricao;

return tarefa;
}
}
61 changes: 61 additions & 0 deletions fontes/tarefas/provedor-tarefas-web.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import * as vscode from 'vscode';

import { comandosLiquido, comandoPorId, TIPO_TAREFA_LIQUIDO, ComandoLiquido } from './comandos-liquido';
import { PseudoterminalLiquido } from './pseudoterminal-liquido';

interface DefinicaoTarefaLiquido extends vscode.TaskDefinition {
type: typeof TIPO_TAREFA_LIQUIDO;
comando: string;
}

/**
* Provedor de tarefas para o host de extensão Web (vscode.dev, github.dev).
*
* O host Web não tem shell nem `child_process`, então `ShellExecution` não
* funciona. Cada comando é executado por `CustomExecution`: o callback devolve
* um `Pseudoterminal` que roda como JS dentro do _web worker_ da extensão.
*
* Só os comandos marcados como `disponivelNaWeb` são oferecidos; o servidor de
* desenvolvimento (que exige HTTP real) é omitido aqui.
*/
export class ProvedorTarefasLiquidoWeb implements vscode.TaskProvider {
static readonly tipo = TIPO_TAREFA_LIQUIDO;

provideTasks(): vscode.Task[] {
return comandosLiquido
.filter(comando => comando.disponivelNaWeb && !comando.requerServidor)
.map(comando => this.criarTarefa(comando));
}

resolveTask(tarefa: vscode.Task): vscode.Task | undefined {
const comando = comandoPorId((tarefa.definition as DefinicaoTarefaLiquido).comando);
if (!comando || !comando.disponivelNaWeb || comando.requerServidor) {
return undefined;
}
return this.criarTarefa(comando, tarefa.definition as DefinicaoTarefaLiquido);
}

private criarTarefa(
comando: ComandoLiquido,
definicao?: DefinicaoTarefaLiquido
): vscode.Task {
const definicaoTarefa: DefinicaoTarefaLiquido = definicao ?? {
type: TIPO_TAREFA_LIQUIDO,
comando: comando.id
};

const execucao = new vscode.CustomExecution(
async (): Promise<vscode.Pseudoterminal> => new PseudoterminalLiquido(comando)
);

const tarefa = new vscode.Task(
definicaoTarefa,
vscode.TaskScope.Workspace,
comando.titulo.replace(/^Liquido:\s*/, ''),
'liquido',
execucao
);
tarefa.detail = comando.descricao;
return tarefa;
}
}
79 changes: 79 additions & 0 deletions fontes/tarefas/pseudoterminal-liquido.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import * as vscode from 'vscode';

import { ComandoLiquido } from './comandos-liquido';

/**
* Pseudoterminal que executa um comando Liquido inteiramente dentro do
* processo de extensão (um _web worker_ no ambiente Web), sem depender de
* shell nem de `child_process` — o caminho recomendado para tarefas no host
* de extensão Web.
*
* A execução de fato dos comandos de scaffolding chama a API JS do
* interpretador/CLI do Liquido e escreve arquivos via `vscode.workspace.fs`.
* Enquanto essa integração não está publicada no pacote Web do Liquido, o
* pseudoterminal informa com clareza o estado de cada comando, em vez de
* falhar em silêncio.
*/
export class PseudoterminalLiquido implements vscode.Pseudoterminal {
private readonly emissorEscrita = new vscode.EventEmitter<string>();
private readonly emissorFechamento = new vscode.EventEmitter<number>();

readonly onDidWrite: vscode.Event<string> = this.emissorEscrita.event;
readonly onDidClose: vscode.Event<number> = this.emissorFechamento.event;

constructor(private readonly comando: ComandoLiquido) {}

async open(): Promise<void> {
const escrever = (linha: string) => this.emissorEscrita.fire(linha + '\r\n');

escrever(`\x1b[36m[Liquido]\x1b[0m ${this.comando.titulo}`);
escrever(this.comando.descricao);
escrever('');

if (this.comando.requerServidor) {
escrever(
'\x1b[33mO servidor de desenvolvimento precisa de HTTP real, indisponível no host de extensão Web.\x1b[0m'
);
escrever(
'Use a extensão de desktop para o servidor, ou uma solução baseada em WebContainers.'
);
this.emissorFechamento.fire(1);
return;
}

try {
await this.executarComando(escrever);
escrever('');
escrever('\x1b[32mConcluído.\x1b[0m');
this.emissorFechamento.fire(0);
} catch (erro) {
escrever('');
escrever(`\x1b[31mFalha:\x1b[0m ${(erro as Error).message}`);
this.emissorFechamento.fire(1);
}
}

close(): void {
this.emissorEscrita.dispose();
this.emissorFechamento.dispose();
}

/**
* Ponto de integração com a API JS do Liquido no worker. Recebe uma função
* de escrita para transmitir a saída ao pseudoterminal.
*/
private async executarComando(escrever: (linha: string) => void): Promise<void> {
const pasta = vscode.workspace.workspaceFolders?.[0];
if (!pasta) {
throw new Error('Nenhuma pasta de trabalho aberta.');
}

escrever(`Diretório do projeto: ${pasta.uri.toString()}`);
escrever(
'\x1b[33mExecução no ambiente Web ainda não está disponível para este comando.\x1b[0m'
);
escrever(
'A base já roteia a tarefa por CustomExecution; falta conectar a API JS do Liquido publicada para Web.'
);
}
}
Loading
Loading