Skip to content

Commit 18f3470

Browse files
Merge pull request #43 from lighttiger2505/add-document-format-option
Add document format options
2 parents 6046f5f + f8d27e9 commit 18f3470

23 files changed

+205
-88
lines changed

internal/config/config.go

+1
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ type Config struct {
2626

2727
func newConfig() *Config {
2828
cfg := &Config{}
29+
cfg.LowercaseKeywords = false
2930
return cfg
3031
}
3132

internal/formatter/formatter.go

+10-2
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,10 @@ func Format(text string, params lsp.DocumentFormattingParams) ([]lsp.TextEdit, e
2727
Line: parsed.End().Line,
2828
Character: parsed.End().Col,
2929
}
30-
formatted := Eval(parsed, &formatEnvironment{})
30+
env := &formatEnvironment{
31+
options: params.Options,
32+
}
33+
formatted := Eval(parsed, env)
3134

3235
res := []lsp.TextEdit{
3336
{
@@ -44,6 +47,7 @@ func Format(text string, params lsp.DocumentFormattingParams) ([]lsp.TextEdit, e
4447
type formatEnvironment struct {
4548
reader *astutil.NodeReader
4649
indentLevel int
50+
options lsp.FormattingOptions
4751
}
4852

4953
func (e *formatEnvironment) indentLevelReset() {
@@ -59,9 +63,13 @@ func (e *formatEnvironment) indentLevelDown() {
5963
}
6064

6165
func (e *formatEnvironment) genIndent() []ast.Node {
66+
indent := whiteSpaceNodes(int(e.options.TabSize))
67+
if !e.options.InsertSpaces {
68+
indent = []ast.Node{tabNode}
69+
}
6270
nodes := []ast.Node{}
6371
for i := 0; i < e.indentLevel; i++ {
64-
nodes = append(nodes, indentNode)
72+
nodes = append(nodes, indent...)
6573
}
6674
return nodes
6775
}

internal/formatter/formatutil.go

+9-1
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,20 @@ var whitespaceNode = ast.NewItem(&token.Token{
1414
Value: " ",
1515
})
1616

17+
func whiteSpaceNodes(num int) []ast.Node {
18+
res := make([]ast.Node, num)
19+
for i := 0; i < num; i++ {
20+
res[i] = whitespaceNode
21+
}
22+
return res
23+
}
24+
1725
var linebreakNode = ast.NewItem(&token.Token{
1826
Kind: token.Whitespace,
1927
Value: "\n",
2028
})
2129

22-
var indentNode = ast.NewItem(&token.Token{
30+
var tabNode = ast.NewItem(&token.Token{
2331
Kind: token.Whitespace,
2432
Value: "\t",
2533
})

internal/handler/format_test.go

+131-84
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package handler
22

33
import (
4+
"fmt"
45
"io/ioutil"
56
"os"
67
"path/filepath"
@@ -11,62 +12,89 @@ import (
1112
"github.com/lighttiger2505/sqls/internal/lsp"
1213
)
1314

14-
func TestFormatting(t *testing.T) {
15+
var formattingOptionTab = lsp.FormattingOptions{
16+
TabSize: 0.0,
17+
InsertSpaces: false,
18+
TrimTrailingWhitespace: false,
19+
InsertFinalNewline: false,
20+
TrimFinalNewlines: false,
21+
}
22+
23+
var formattingOptionIndentSpace2 = lsp.FormattingOptions{
24+
TabSize: 2.0,
25+
InsertSpaces: true,
26+
TrimTrailingWhitespace: false,
27+
InsertFinalNewline: false,
28+
TrimFinalNewlines: false,
29+
}
30+
31+
var formattingOptionIndentSpace4 = lsp.FormattingOptions{
32+
TabSize: 4.0,
33+
InsertSpaces: true,
34+
TrimTrailingWhitespace: false,
35+
InsertFinalNewline: false,
36+
TrimFinalNewlines: false,
37+
}
38+
39+
type formattingTestCase struct {
40+
name string
41+
input string
42+
want string
43+
}
44+
45+
func testFormatting(t *testing.T, testCases []formattingTestCase, options lsp.FormattingOptions) {
1546
tx := newTestContext()
1647
tx.initServer(t)
1748
defer tx.tearDown()
1849

1950
uri := "file:///Users/octref/Code/css-test/test.sql"
51+
for _, tt := range testCases {
52+
t.Run(tt.name, func(t *testing.T) {
53+
// Open dummy file
54+
didOpenParams := lsp.DidOpenTextDocumentParams{
55+
TextDocument: lsp.TextDocumentItem{
56+
URI: uri,
57+
LanguageID: "sql",
58+
Version: 0,
59+
Text: tt.input,
60+
},
61+
}
62+
if err := tx.conn.Call(tx.ctx, "textDocument/didOpen", didOpenParams, nil); err != nil {
63+
t.Fatal("conn.Call textDocument/didOpen:", err)
64+
}
65+
tx.testFile(t, didOpenParams.TextDocument.URI, didOpenParams.TextDocument.Text)
66+
// Create completion params
67+
formattingParams := lsp.DocumentFormattingParams{
68+
TextDocument: lsp.TextDocumentIdentifier{
69+
URI: uri,
70+
},
71+
Options: options,
72+
WorkDoneProgressParams: lsp.WorkDoneProgressParams{
73+
WorkDoneToken: nil,
74+
},
75+
}
2076

21-
type formattingTestCase struct {
22-
name string
23-
input string
24-
want string
77+
var got []lsp.TextEdit
78+
if err := tx.conn.Call(tx.ctx, "textDocument/formatting", formattingParams, &got); err != nil {
79+
t.Fatal("conn.Call textDocument/formatting:", err)
80+
}
81+
if diff := cmp.Diff(tt.want, got[0].NewText); diff != "" {
82+
t.Errorf("unmatch (- want, + got):\n%s", diff)
83+
t.Errorf("unmatch\nwant: %q\ngot : %q", tt.want, got[0].NewText)
84+
}
85+
})
2586
}
87+
}
2688

27-
testDir, err := os.Getwd()
28-
if err != nil {
29-
t.Fatal(err)
30-
}
31-
testFileInfos, err := ioutil.ReadDir("testdata")
89+
func TestFormattingBase(t *testing.T) {
90+
testCase, err := loadFormatTestCaseByTestdata("format")
3291
if err != nil {
3392
t.Fatal(err)
3493
}
94+
testFormatting(t, testCase, formattingOptionTab)
95+
}
3596

36-
testCase := []formattingTestCase{}
37-
38-
// Add golden file test
39-
const (
40-
inputFileSuffix = ".input.sql"
41-
goldenFileSuffix = ".golden.sql"
42-
)
43-
for _, testFileInfo := range testFileInfos {
44-
inputFileName := testFileInfo.Name()
45-
if !strings.HasSuffix(inputFileName, inputFileSuffix) {
46-
continue
47-
}
48-
49-
testName := testFileInfo.Name()[:len(inputFileName)-len(inputFileSuffix)]
50-
inputPath := filepath.Join(testDir, "testdata", inputFileName)
51-
goldenPath := filepath.Join(testDir, "testdata", testName+goldenFileSuffix)
52-
53-
input, err := ioutil.ReadFile(inputPath)
54-
if err != nil {
55-
t.Errorf("Cannot read input file, Path=%s, Err=%+v", inputPath, err)
56-
continue
57-
}
58-
golden, err := ioutil.ReadFile(goldenPath)
59-
if err != nil {
60-
t.Errorf("Cannot read input file, Path=%s, Err=%+v", goldenPath, err)
61-
continue
62-
}
63-
testCase = append(testCase, formattingTestCase{
64-
name: testName,
65-
input: string(input),
66-
want: string(golden)[:len(string(golden))-len("\n")],
67-
})
68-
}
69-
97+
func TestFormattingMinimal(t *testing.T) {
7098
// Add minimal case test
7199
minimalTestCase := []formattingTestCase{
72100
{
@@ -105,48 +133,67 @@ func TestFormatting(t *testing.T) {
105133
want: "1,\n2,\n3,\n4",
106134
},
107135
}
108-
testCase = append(testCase, minimalTestCase...)
136+
testFormatting(t, minimalTestCase, formattingOptionTab)
137+
}
109138

110-
for _, tt := range testCase {
111-
t.Run(tt.name, func(t *testing.T) {
112-
// Open dummy file
113-
didOpenParams := lsp.DidOpenTextDocumentParams{
114-
TextDocument: lsp.TextDocumentItem{
115-
URI: uri,
116-
LanguageID: "sql",
117-
Version: 0,
118-
Text: tt.input,
119-
},
120-
}
121-
if err := tx.conn.Call(tx.ctx, "textDocument/didOpen", didOpenParams, nil); err != nil {
122-
t.Fatal("conn.Call textDocument/didOpen:", err)
123-
}
124-
tx.testFile(t, didOpenParams.TextDocument.URI, didOpenParams.TextDocument.Text)
125-
// Create completion params
126-
formattingParams := lsp.DocumentFormattingParams{
127-
TextDocument: lsp.TextDocumentIdentifier{
128-
URI: uri,
129-
},
130-
Options: lsp.FormattingOptions{
131-
TabSize: 0.0,
132-
InsertSpaces: false,
133-
TrimTrailingWhitespace: false,
134-
InsertFinalNewline: false,
135-
TrimFinalNewlines: false,
136-
},
137-
WorkDoneProgressParams: lsp.WorkDoneProgressParams{
138-
WorkDoneToken: nil,
139-
},
140-
}
139+
func TestFormattingWithOptionSpace2(t *testing.T) {
140+
testCase, err := loadFormatTestCaseByTestdata("format_option_space2")
141+
if err != nil {
142+
t.Fatal(err)
143+
}
144+
testFormatting(t, testCase, formattingOptionIndentSpace2)
145+
}
141146

142-
var got []lsp.TextEdit
143-
if err := tx.conn.Call(tx.ctx, "textDocument/formatting", formattingParams, &got); err != nil {
144-
t.Fatal("conn.Call textDocument/formatting:", err)
145-
}
146-
if diff := cmp.Diff(tt.want, got[0].NewText); diff != "" {
147-
t.Errorf("unmatch (- want, + got):\n%s", diff)
148-
t.Errorf("unmatch\nwant: %q\ngot : %q", tt.want, got[0].NewText)
149-
}
147+
func TestFormattingWithOptionSpace4(t *testing.T) {
148+
testCase, err := loadFormatTestCaseByTestdata("format_option_space4")
149+
if err != nil {
150+
t.Fatal(err)
151+
}
152+
testFormatting(t, testCase, formattingOptionIndentSpace4)
153+
}
154+
155+
func loadFormatTestCaseByTestdata(targetDir string) ([]formattingTestCase, error) {
156+
packageDir, err := os.Getwd()
157+
if err != nil {
158+
return nil, err
159+
}
160+
testDir := filepath.Join(packageDir, "testdata", targetDir)
161+
testFileInfos, err := ioutil.ReadDir(testDir)
162+
if err != nil {
163+
return nil, err
164+
}
165+
166+
testCase := []formattingTestCase{}
167+
const (
168+
inputFileSuffix = ".input.sql"
169+
goldenFileSuffix = ".golden.sql"
170+
)
171+
172+
for _, testFileInfo := range testFileInfos {
173+
inputFileName := testFileInfo.Name()
174+
if !strings.HasSuffix(inputFileName, inputFileSuffix) {
175+
continue
176+
}
177+
178+
testName := testFileInfo.Name()[:len(inputFileName)-len(inputFileSuffix)]
179+
inputPath := filepath.Join(testDir, inputFileName)
180+
goldenPath := filepath.Join(testDir, testName+goldenFileSuffix)
181+
182+
input, err := ioutil.ReadFile(inputPath)
183+
if err != nil {
184+
return nil, fmt.Errorf("Cannot read input file, Path=%s, Err=%+v", inputPath, err)
185+
continue
186+
}
187+
golden, err := ioutil.ReadFile(goldenPath)
188+
if err != nil {
189+
return nil, fmt.Errorf("Cannot read input file, Path=%s, Err=%+v", goldenPath, err)
190+
continue
191+
}
192+
testCase = append(testCase, formattingTestCase{
193+
name: testName,
194+
input: string(input),
195+
want: string(golden)[:len(string(golden))-len("\n")],
150196
})
151197
}
198+
return testCase, nil
152199
}

internal/handler/handler.go

+2-1
Original file line numberDiff line numberDiff line change
@@ -382,7 +382,8 @@ func (s *Server) getConfig() *config.Config {
382382
}
383383

384384
func validConfig(cfg *config.Config) bool {
385-
if cfg != nil && len(cfg.Connections) > 0 {
385+
// if cfg != nil && len(cfg.Connections) > 0 {
386+
if cfg != nil {
386387
return true
387388
}
388389
return false
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
select
2+
a,
3+
b as bb,
4+
c
5+
from
6+
table
7+
join (
8+
select
9+
a * 2 as a
10+
from
11+
new_table
12+
) other
13+
on table.a = other.a
14+
where
15+
c is true
16+
and b between 3
17+
and 4
18+
or d is 'blue'
19+
limit 10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
select a, b as bb,c from table
2+
join (select a * 2 as a from new_table) other
3+
on table.a = other.a
4+
where c is true
5+
and b between 3 and 4
6+
or d is 'blue'
7+
limit 10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
select
2+
a,
3+
b as bb,
4+
c
5+
from
6+
table
7+
join (
8+
select
9+
a * 2 as a
10+
from
11+
new_table
12+
) other
13+
on table.a = other.a
14+
where
15+
c is true
16+
and b between 3
17+
and 4
18+
or d is 'blue'
19+
limit 10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
select a, b as bb,c from table
2+
join (select a * 2 as a from new_table) other
3+
on table.a = other.a
4+
where c is true
5+
and b between 3 and 4
6+
or d is 'blue'
7+
limit 10

0 commit comments

Comments
 (0)