isColumnContext returns true if the cursor position appears to be in a context where column names should be suggested. This covers: - Between SELECT and FROM (the projection list) - After FROM when inside a clause that takes expressions: WHERE, HAVING, SET, ORDER BY, GROUP BY, and any operator/funct
(statement string, pos int)
| 143 | // |
| 144 | // Table-only contexts (immediately after FROM/JOIN/INTO) return false. |
| 145 | func isColumnContext(statement string, pos int) bool { |
| 146 | if pos > len(statement) { |
| 147 | pos = len(statement) |
| 148 | } |
| 149 | // Strip quoted identifiers before uppercasing so that "order" doesn't |
| 150 | // match the ORDER keyword in lastKeywordIndex. |
| 151 | before := strings.ToUpper(stripQuotedIdentifiers(statement[:pos])) |
| 152 | |
| 153 | // Strip any partial identifier the user is currently typing. |
| 154 | trimmed := strings.TrimRight(before, "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_$\"") |
| 155 | trimmed = strings.TrimRight(trimmed, " \t\n\r") |
| 156 | |
| 157 | // Table context: cursor is right after FROM/JOIN/INTO. |
| 158 | if strings.HasSuffix(trimmed, "FROM") || |
| 159 | strings.HasSuffix(trimmed, "JOIN") || |
| 160 | strings.HasSuffix(trimmed, "INTO") { |
| 161 | return false |
| 162 | } |
| 163 | |
| 164 | // Between SELECT and FROM (the projection list) — column context. |
| 165 | selectIdx := lastKeywordIndex(before, "SELECT") |
| 166 | fromIdx := lastKeywordIndex(before, "FROM") |
| 167 | if selectIdx >= 0 && (fromIdx < 0 || fromIdx < selectIdx) { |
| 168 | return true |
| 169 | } |
| 170 | |
| 171 | // After FROM: if any expression-bearing clause keyword appears in the |
| 172 | // text before the cursor, we're in a column context. |
| 173 | for _, kw := range []string{"WHERE", "HAVING", "SET", "ORDER", "GROUP", "BY", "AND", "OR", "ON", "WHEN", "THEN", "ELSE"} { |
| 174 | kwIdx := lastKeywordIndex(before, kw) |
| 175 | if kwIdx >= 0 && kwIdx > fromIdx { |
| 176 | return true |
| 177 | } |
| 178 | } |
| 179 | |
| 180 | // Fallback: if FROM was seen and the cursor is past it in a position |
| 181 | // that is NOT right after FROM/JOIN/INTO (already excluded above), |
| 182 | // we're likely in an expression context within a clause. |
| 183 | if fromIdx >= 0 { |
| 184 | for _, kw := range []string{"WHERE", "HAVING", "SET", "ORDER", "GROUP"} { |
| 185 | if lastKeywordIndex(before, kw) > fromIdx { |
| 186 | return true |
| 187 | } |
| 188 | } |
| 189 | } |
| 190 | |
| 191 | return false |
| 192 | } |
| 193 | |
| 194 | // lastKeywordIndex returns the index of the last occurrence of kw in s |
| 195 | // that appears at a word boundary (not inside an identifier). Returns -1 |
no test coverage detected