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
101 changes: 50 additions & 51 deletions crates/squawk/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -216,62 +216,61 @@ Please open an issue at https://github.com/sbdchd/squawk/issues/new with the log

let mut clap_app = Opt::clap();
let is_stdin = !atty::is(Stream::Stdin);

if let Some(subcommand) = opts.cmd {
match subcommand {
Command::Server => {
squawk_server::run().context("language server failed")?;
}
Command::UploadToGithub(args) => {
github::check_and_comment_on_pr(
args,
&conf,
is_stdin,
opts.stdin_filepath,
&excluded_rules,
&excluded_paths,
pg_version,
assume_in_transaction,
)
.context("Upload to GitHub failed")?;
}
match opts.cmd {
Some(Command::Server) => {
squawk_server::run().context("language server failed")?;
}
} else {
let found_paths = find_paths(&opts.path_patterns, &excluded_paths).unwrap_or_else(|e| {
eprintln!("Failed to find files: {e}");
process::exit(1);
});
if found_paths.is_empty() && !opts.path_patterns.is_empty() {
eprintln!(
"Failed to find files for provided patterns: {:?}",
opts.path_patterns
);
process::exit(1);
Some(Command::UploadToGithub(args)) => {
github::check_and_comment_on_pr(
args,
&conf,
is_stdin,
opts.stdin_filepath,
&excluded_rules,
&excluded_paths,
pg_version,
assume_in_transaction,
)
.context("Upload to GitHub failed")?;
}
if !found_paths.is_empty() || is_stdin {
let stdout = io::stdout();
let mut handle = stdout.lock();
None => {
let found_paths =
find_paths(&opts.path_patterns, &excluded_paths).unwrap_or_else(|e| {
eprintln!("Failed to find files: {e}");
process::exit(1);
});
if found_paths.is_empty() && !opts.path_patterns.is_empty() {
eprintln!(
"Failed to find files for provided patterns: {:?}",
opts.path_patterns
);
process::exit(1);
}
if !found_paths.is_empty() || is_stdin {
let stdout = io::stdout();
let mut handle = stdout.lock();

let read_stdin = found_paths.is_empty() && is_stdin;
if let Some(kind) = opts.debug {
debug(&mut handle, &found_paths, read_stdin, &kind, opts.verbose)?;
let read_stdin = found_paths.is_empty() && is_stdin;
if let Some(kind) = opts.debug {
debug(&mut handle, &found_paths, read_stdin, &kind, opts.verbose)?;
} else {
let reporter = opts.reporter.unwrap_or(Reporter::Tty);
let exit_code = check_and_dump_files(
&mut handle,
&found_paths,
read_stdin,
opts.stdin_filepath,
&excluded_rules,
pg_version,
assume_in_transaction,
&reporter,
)?;
return Ok(exit_code);
}
} else {
let reporter = opts.reporter.unwrap_or(Reporter::Tty);
let exit_code = check_and_dump_files(
&mut handle,
&found_paths,
read_stdin,
opts.stdin_filepath,
&excluded_rules,
pg_version,
assume_in_transaction,
&reporter,
)?;
return Ok(exit_code);
clap_app.print_long_help()?;
println!();
}
} else {
clap_app.print_long_help()?;
println!();
}
}
Ok(ExitCode::SUCCESS)
Expand Down
9 changes: 8 additions & 1 deletion squawk-vscode/eslint.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,18 @@ import tseslint from "typescript-eslint"
export default tseslint.config(
{ ignores: ["out", "extension", "dist"] },
{
extends: [js.configs.recommended, ...tseslint.configs.recommended],
extends: [
js.configs.recommended,
...tseslint.configs.recommendedTypeChecked,
],
files: ["**/*.{ts,tsx}"],
languageOptions: {
ecmaVersion: 2020,
globals: globals.node,
parserOptions: {
project: "./tsconfig.json",
tsconfigRootDir: import.meta.dirname,
},
},
plugins: {},
rules: {
Expand Down
10 changes: 10 additions & 0 deletions squawk-vscode/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,16 @@
"command": "squawk.showClientLogs",
"title": "Show Client Logs",
"category": "Squawk"
},
{
"command": "squawk.startServer",
"title": "Start Server",
"category": "Squawk"
},
{
"command": "squawk.stopServer",
"title": "Stop Server",
"category": "Squawk"
}
],
"languages": [
Expand Down
154 changes: 138 additions & 16 deletions squawk-vscode/src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,16 @@ import {
LanguageClientOptions,
Executable,
ServerOptions,
State,
StateChangeEvent,
} from "vscode-languageclient/node"

let client: LanguageClient | undefined
let log: Pick<
vscode.LogOutputChannel,
"trace" | "debug" | "info" | "warn" | "error" | "show"
>
const onClientStateChange = new vscode.EventEmitter<StateChangeEvent>()

export async function activate(context: vscode.ExtensionContext) {
log = vscode.window.createOutputChannel("Squawk Client", {
Expand Down Expand Up @@ -41,11 +44,15 @@ export async function activate(context: vscode.ExtensionContext) {
)
return version
} catch (error) {
vscode.window.showErrorMessage(`Failed to get server version: ${error}`)
vscode.window.showErrorMessage(
`Failed to get server version: ${String(error)}`,
)
}
}),
)

setupStatusBarItem(context)

context.subscriptions.push(
vscode.commands.registerCommand("squawk.showLogs", () => {
client?.outputChannel?.show()
Expand All @@ -58,15 +65,19 @@ export async function activate(context: vscode.ExtensionContext) {
}),
)

const statusBarItem = vscode.window.createStatusBarItem(
vscode.StatusBarAlignment.Right,
100,
context.subscriptions.push(
vscode.commands.registerCommand("squawk.startServer", async () => {
await startServer(context)
}),
)
statusBarItem.text = "Squawk"
statusBarItem.tooltip = "Click to show Squawk Language Server logs"
statusBarItem.command = "squawk.showLogs"
statusBarItem.show()
context.subscriptions.push(statusBarItem)

context.subscriptions.push(
vscode.commands.registerCommand("squawk.stopServer", async () => {
await stopServer()
}),
)

context.subscriptions.push(onClientStateChange)

await startServer(context)
}
Expand All @@ -83,12 +94,88 @@ function isSqlEditor(editor: vscode.TextEditor): boolean {
return isSqlDocument(editor.document)
}

function setupStatusBarItem(context: vscode.ExtensionContext) {
const statusBarItem = vscode.window.createStatusBarItem(
vscode.StatusBarAlignment.Left,
)
statusBarItem.text = "Squawk"
statusBarItem.command = "squawk.showLogs"
context.subscriptions.push(statusBarItem)

const onDidChangeActiveTextEditor = (
editor: vscode.TextEditor | undefined,
) => {
if (editor && isSqlEditor(editor)) {
updateStatusBarItem(statusBarItem)
statusBarItem.show()
} else {
statusBarItem.hide()
}
}

onDidChangeActiveTextEditor(vscode.window.activeTextEditor)

context.subscriptions.push(
vscode.window.onDidChangeActiveTextEditor((editor) => {
onDidChangeActiveTextEditor(editor)
}),
)

context.subscriptions.push(
onClientStateChange.event(() => {
updateStatusBarItem(statusBarItem)
}),
)
}

function updateStatusBarItem(statusBarItem: vscode.StatusBarItem) {
if (!client) {
return
}
let statusText: string
let icon: string
let backgroundColor: vscode.ThemeColor | undefined
switch (client.state) {
case State.Stopped:
statusText = "Stopped"
icon = "$(error) "
backgroundColor = new vscode.ThemeColor("statusBarItem.warningBackground")
break
case State.Starting:
statusText = "Starting..."
icon = "$(loading~spin) "
backgroundColor = undefined
break
case State.Running:
statusText = "Running"
icon = ""
backgroundColor = undefined
break
default:
assertNever(client.state)
}

statusBarItem.text = `${icon}Squawk`
statusBarItem.backgroundColor = backgroundColor
statusBarItem.tooltip = `Status: ${statusText}\nClick to show server logs`
}

function getSquawkPath(context: vscode.ExtensionContext): vscode.Uri {
const ext = process.platform === "win32" ? ".exe" : ""
return vscode.Uri.joinPath(context.extensionUri, "server", `squawk${ext}`)
}

async function startServer(context: vscode.ExtensionContext) {
if (client?.state === State.Running) {
log.info("Server is already running")
return
}

if (client?.state === State.Starting) {
log.info("Server is already starting")
return
}

log.info("Starting Squawk Language Server...")

const squawkPath = getSquawkPath(context)
Expand Down Expand Up @@ -120,9 +207,38 @@ async function startServer(context: vscode.ExtensionContext) {
clientOptions,
)

log.info("Language client created, starting...")
client.start()
log.info("Language client started")
context.subscriptions.push(
client.onDidChangeState((event) => {
onClientStateChange.fire(event)
}),
)

log.info("server starting...")
try {
await client.start()
log.info("server started successfully")
} catch (error) {
log.error(`Failed to start server:`, error)
vscode.window.showErrorMessage(`Failed to start server: ${String(error)}`)
}
}

async function stopServer() {
if (!client) {
log.info("No client to stop server")
return
}

if (client.state === State.Stopped) {
log.info("Server is already stopped")
return
}

log.info("Stopping server...")

await client.stop()

log.info("server stopped")
}

// Based on rust-analyzer's SyntaxTree support:
Expand Down Expand Up @@ -181,18 +297,24 @@ class SyntaxTreeProvider implements vscode.TextDocumentContentProvider {
if (!document) {
return "Error: no active editor found"
}
if (!client) {
return "Error: no client found"
}
const text = document.getText()
const uri = document.uri.toString()
log.info(`Requesting syntax tree for: ${uri}`)
const response = await client?.sendRequest("squawk/syntaxTree", {
const response = await client.sendRequest<string>("squawk/syntaxTree", {
textDocument: { uri },
text,
})
log.info("Syntax tree received")
return response as string
return response
} catch (error) {
log.error(`Failed to get syntax tree: ${error}`)
return `Error: Failed to get syntax tree: ${error}`
log.error(`Failed to get syntax tree:`, error)
return `Error: Failed to get syntax tree: ${String(error)}`
}
}
}
function assertNever(param: never): never {
throw new Error(`should never get here, but got ${String(param)}`)
}
Loading