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
61 changes: 59 additions & 2 deletions src/composables/useSqlStatements.ts
Original file line number Diff line number Diff line change
Expand Up @@ -207,13 +207,70 @@ function lineAtOffset(content: string, offset: number): number {
return line
}

/**
* Count leading lines in `text` that are whitespace-only or comment-only
* (`--` line comments, `/ * ... * /` block comments). Used to position the
* gutter execute icon on the first line with actual SQL content.
*/
function skipLeadingComments(text: string): number {
const lines = text.split('\n')
let inBlockComment = false
let skipped = 0

for (const line of lines) {
if (inBlockComment) {
const endIdx = line.indexOf('*/')
if (endIdx !== -1) {
inBlockComment = false
if (line.slice(endIdx + 2).trim().length > 0) {
// SQL after block comment on same line — stop
break
}
}
skipped++
continue
}

const trimmed = line.trim()

if (trimmed.length === 0) {
skipped++
continue
}

if (trimmed.startsWith('--')) {
skipped++
continue
}

if (trimmed.startsWith('/*')) {
const endIdx = trimmed.indexOf('*/')
if (endIdx === -1) {
inBlockComment = true
skipped++
continue
}
if (trimmed.slice(endIdx + 2).trim().length > 0) {
// SQL after inline block comment on same line
break
}
skipped++
continue
}

// First line with actual SQL content
break
}

return skipped
}

export function parseSqlStatements(content: string): SqlStatement[] {
const ranges = scanStatements(content)
const lines = content.split('\n')

return ranges
.filter((r) => {
// Remove trailing semicolon for the statement text
const t = r.text.trim().replace(/;\s*$/, '').trim()
return t.length > 0
})
Expand All @@ -222,7 +279,7 @@ export function parseSqlStatements(content: string): SqlStatement[] {
return {
statement,
position: {
startLineNumber: r.startLine,
startLineNumber: r.startLine + skipLeadingComments(r.text),
endLineNumber: r.endLine,
startColumn: 1,
endColumn: (lines[r.endLine - 1]?.length ?? 1) + 1,
Expand Down
47 changes: 47 additions & 0 deletions tests/composables/useSqlStatements.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,53 @@ describe('parseSqlStatements', () => {
})
})

describe('statement positions (gutter icon alignment)', () => {
it('records correct startLineNumber for second statement when blank lines separate them', () => {
const sql = [
'SELECT 1;', // line 1
'', // line 2 (blank)
'', // line 3 (blank)
'SELECT 2;', // line 4
].join('\n')

const result = parseSqlStatements(sql)
expect(result).toHaveLength(2)
// First statement starts at line 1
expect(result[0].position.startLineNumber).toBe(1)
// Second statement should start at line 4 (first non-blank line),
// NOT at the line after the ; (line 1)
expect(result[1].position.startLineNumber).toBe(4)
})

it('records correct startLineNumber when preceded by blank lines and comment', () => {
const sql = [
'SELECT 1;', // line 1
'', // line 2 (blank)
'', // line 3 (blank)
'-- comment', // line 4
'SELECT 2;', // line 5
].join('\n')

const result = parseSqlStatements(sql)
expect(result).toHaveLength(2)
// Icon should be at line 5 (first SQL line), not at the -- comment (line 4)
// or the blank after ; (line 1)
expect(result[1].position.startLineNumber).toBe(5)
})

it('keeps startLineNumber unchanged when there is no leading blank', () => {
const sql = [
'SELECT 1;',
'SELECT 2;',
].join('\n')

const result = parseSqlStatements(sql)
expect(result).toHaveLength(2)
expect(result[0].position.startLineNumber).toBe(1)
expect(result[1].position.startLineNumber).toBe(2)
})
})

describe('nested parens (subqueries)', () => {
it('does not split on SELECT inside a subquery', () => {
const sql = [
Expand Down
Loading