Skip to content

Commit 0e49fc1

Browse files
authored
feat: Workspaces and File API (#2)
* feat: Add websocket connection * feat: Socket Workspaces * fix: workflow * update: move to gorilla websockets * feat: File API and LSP Instances
1 parent 2027796 commit 0e49fc1

File tree

17 files changed

+280
-5
lines changed

17 files changed

+280
-5
lines changed

.githooks/pre-commit

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ echo "\033[36m
55
Running golangci-lint
66
======================================================================"
77

8-
golangci-lint run .
8+
golangci-lint run . --config .golangci.yaml
99

1010
if [ $? != 0 ]; then
1111
echo "\033[31m

.github/workflows/golangci-lint.yml

+2-2
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ on:
99

1010
permissions:
1111
contents: read
12-
12+
1313
jobs:
1414
golangci:
1515
name: Lint
@@ -23,4 +23,4 @@ jobs:
2323
uses: golangci/golangci-lint-action@v3
2424
with:
2525
version: latest
26-
args: "--out-${NO_FUTURE}format colored-line-number --print-issued-lines --print-linter-name"
26+
args: "--config .golangci.yaml --out-${NO_FUTURE}format colored-line-number --print-issued-lines --print-linter-name"

.gitignore

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
.env
22
server
3-
.vscode
3+
.vscode
4+
workspaces

Dockerfile

+1-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ FROM build AS dev
1515

1616
WORKDIR /app
1717

18-
RUN apk add --no-cache make
18+
RUN apk add --no-cache make ccls
1919

2020
RUN go install github.com/cespare/reflex@latest
2121

controllers/.gitkeep

Whitespace-only changes.

controllers/file.go

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package controllers
2+
3+
import (
4+
"fmt"
5+
"io/ioutil"
6+
7+
"github.com/delta/codecharacter-lsp-2023/models"
8+
)
9+
10+
func handleFileUpdate(message map[string]interface{}, ws *models.WebsocketConnection) error {
11+
fmt.Println("Processing File Update Request")
12+
var filename string
13+
switch ws.Language {
14+
case "cpp":
15+
filename = "run.cpp"
16+
case "java":
17+
filename = "run.java"
18+
case "python":
19+
filename = "run.py"
20+
}
21+
err := ioutil.WriteFile(ws.WorkspacePath+"/"+filename, []byte(message["code"].(string)), 0644)
22+
if err != nil {
23+
fmt.Println(err)
24+
return err
25+
}
26+
return nil
27+
}

controllers/message.go

+48
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
package controllers
2+
3+
import (
4+
"encoding/json"
5+
"fmt"
6+
7+
"github.com/delta/codecharacter-lsp-2023/models"
8+
"github.com/gorilla/websocket"
9+
)
10+
11+
func HandleMessage(ws *models.WebsocketConnection, messageBytes []byte) error {
12+
var message map[string]interface{}
13+
err := json.Unmarshal(messageBytes, &message)
14+
if err != nil {
15+
return err
16+
}
17+
_, isPresent := message["jsonrpc"]
18+
fmt.Println("Is JSONRPC? : ", isPresent)
19+
if isPresent {
20+
return handleJSONRPCRequest(ws, messageBytes)
21+
}
22+
return handleWebSocketRequest(ws, message)
23+
}
24+
25+
func SendMessage(ws *models.WebsocketConnection, message map[string]interface{}) error {
26+
messageBytes, err := json.Marshal(message)
27+
if err != nil {
28+
return err
29+
}
30+
err = ws.Connection.WriteMessage(websocket.TextMessage, messageBytes)
31+
if err != nil {
32+
return err
33+
}
34+
return nil
35+
}
36+
37+
func handleJSONRPCRequest(ws *models.WebsocketConnection, messageBytes []byte) error {
38+
fmt.Println("JSONRPC Request : ", string(messageBytes), " with ID : ", ws.ID)
39+
return nil
40+
}
41+
42+
func handleWebSocketRequest(ws *models.WebsocketConnection, message map[string]interface{}) error {
43+
fmt.Println("Websocket Request : ", message, " with ID : ", ws.ID)
44+
if message["operation"] == "fileUpdate" {
45+
return handleFileUpdate(message, ws)
46+
}
47+
return nil
48+
}

docker-compose.yml

+1
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,4 @@ services:
1111
- ${SERVER_PORT}:8000
1212
volumes:
1313
- .:/app
14+
- ./workspaces:/app/workspaces

go.mod

+3
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,11 @@ require (
88
github.com/labstack/echo/v4 v4.9.0
99
)
1010

11+
require github.com/gorilla/websocket v1.5.0
12+
1113
require (
1214
github.com/golang-jwt/jwt v3.2.2+incompatible // indirect
15+
github.com/google/uuid v1.3.0
1316
github.com/labstack/gommon v0.4.0 // indirect
1417
github.com/mattn/go-colorable v0.1.13 // indirect
1518
github.com/mattn/go-isatty v0.0.16 // indirect

go.sum

+4
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@ github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w=
55
github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
66
github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY=
77
github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
8+
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
9+
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
10+
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
11+
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
812
github.com/joho/godotenv v1.4.0 h1:3l4+N6zfMWnkbPEXKng2o2/MR5mSwTrBih4ZEkkz1lg=
913
github.com/joho/godotenv v1.4.0/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
1014
github.com/labstack/echo/v4 v4.9.0 h1:wPOF1CE6gvt/kmbMR4dGzWvHMPT+sAEUJOwOTtvITVY=

main.go

+2
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package main
22

33
import (
44
"github.com/delta/codecharacter-lsp-2023/config"
5+
"github.com/delta/codecharacter-lsp-2023/router"
56
"github.com/delta/codecharacter-lsp-2023/utils"
67

78
"github.com/labstack/echo/v4"
@@ -14,6 +15,7 @@ func main() {
1415
utils.InitLogger(server)
1516
server.Use(middleware.CORS())
1617
config.InitConfig()
18+
router.InitRoutes(server)
1719

1820
server.Logger.Fatal(server.Start(":" + config.ServerPort))
1921
}

models/websocket.go

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package models
2+
3+
import (
4+
"os/exec"
5+
6+
"github.com/google/uuid"
7+
"github.com/gorilla/websocket"
8+
)
9+
10+
type WebsocketConnection struct {
11+
ID uuid.UUID
12+
Connection *websocket.Conn
13+
Language string
14+
WorkspacePath string
15+
LSPServer *exec.Cmd
16+
}

router/home.go

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package router
2+
3+
import (
4+
"net/http"
5+
6+
"github.com/labstack/echo/v4"
7+
)
8+
9+
func home(c echo.Context) error {
10+
return c.String(http.StatusOK, "Hello! Welcome to the CodeCharacter LSP Project")
11+
}

router/router.go

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package router
2+
3+
import "github.com/labstack/echo/v4"
4+
5+
func InitRoutes(server *echo.Echo) {
6+
server.GET("/", home)
7+
server.GET("/ws/:language", handleWebSocketConnection)
8+
}

router/websocket.go

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package router
2+
3+
import (
4+
"github.com/delta/codecharacter-lsp-2023/utils"
5+
"github.com/labstack/echo/v4"
6+
)
7+
8+
func handleWebSocketConnection(c echo.Context) error {
9+
err := utils.InitWebsocket(c)
10+
if err != nil {
11+
return err
12+
}
13+
return nil
14+
}

utils/lsp.go

+42
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package utils
2+
3+
import (
4+
"fmt"
5+
"os"
6+
"os/exec"
7+
8+
"github.com/delta/codecharacter-lsp-2023/models"
9+
)
10+
11+
func CreateLSPServer(wsConnectionParams *models.WebsocketConnection) error {
12+
switch wsConnectionParams.Language {
13+
case "cpp":
14+
return createCppServer(wsConnectionParams)
15+
}
16+
return nil
17+
}
18+
19+
func createCppServer(wsConnectionParams *models.WebsocketConnection) error {
20+
wsConnectionParams.LSPServer = exec.Command("ccls", `--init={
21+
"index":{
22+
"onChange":true,
23+
"trackDependency":0,
24+
"threads":2,
25+
"comments":0
26+
},
27+
"cache":{
28+
"retainInMemory":0,
29+
"directory":"./`+wsConnectionParams.WorkspacePath+`"
30+
},
31+
"diagnostics":{
32+
"onSave":1500
33+
}
34+
}`)
35+
wsConnectionParams.LSPServer.Stderr = os.Stderr
36+
err := wsConnectionParams.LSPServer.Start()
37+
if err != nil {
38+
fmt.Println(err)
39+
return err
40+
}
41+
return nil
42+
}

utils/websocket.go

+98
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
package utils
2+
3+
import (
4+
"fmt"
5+
"net/http"
6+
"os"
7+
8+
"github.com/delta/codecharacter-lsp-2023/controllers"
9+
"github.com/delta/codecharacter-lsp-2023/models"
10+
"github.com/google/uuid"
11+
"github.com/gorilla/websocket"
12+
"github.com/labstack/echo/v4"
13+
)
14+
15+
var upgrader = websocket.Upgrader{}
16+
17+
func InitWebsocket(c echo.Context) error {
18+
var ws models.WebsocketConnection
19+
id := uuid.New()
20+
ws.ID = id
21+
language := c.Param("language")
22+
if language != "cpp" && language != "java" && language != "python" {
23+
return c.String(http.StatusBadRequest, "Invalid Language")
24+
}
25+
ws.Language = language
26+
fmt.Println("WS Connection Created with ID : ", id, " and Language : ", language)
27+
wsConn, err := upgrader.Upgrade(c.Response(), c.Request(), nil)
28+
if err != nil {
29+
return c.String(http.StatusBadRequest, "Error Upgrading to Websocket Connection")
30+
}
31+
ws.Connection = wsConn
32+
err = createWebsocketConnection(&ws)
33+
if err != nil {
34+
return c.String(http.StatusBadGateway, "Something went wrong, contact the event administrator.")
35+
}
36+
return nil
37+
}
38+
39+
func dropConnection(ws *models.WebsocketConnection) {
40+
err := os.RemoveAll(ws.WorkspacePath)
41+
if err != nil {
42+
fmt.Println(err)
43+
}
44+
if ws.LSPServer != nil {
45+
err = ws.LSPServer.Process.Signal(os.Interrupt)
46+
if err != nil {
47+
fmt.Println(err)
48+
}
49+
// Reads process exit state to remove the <defunct> process from the system process table
50+
err = ws.LSPServer.Wait()
51+
if err != nil {
52+
fmt.Println(err)
53+
}
54+
}
55+
ws.Connection.Close()
56+
fmt.Println("Dropped WS Connection : ", ws.ID)
57+
}
58+
59+
func createWebsocketConnection(ws *models.WebsocketConnection) error {
60+
defer dropConnection(ws)
61+
62+
ws.WorkspacePath = "workspaces/" + ws.ID.String()
63+
err := os.Mkdir(ws.WorkspacePath, os.ModePerm)
64+
if err != nil {
65+
fmt.Println(err)
66+
return err
67+
}
68+
err = CreateLSPServer(ws)
69+
if err != nil {
70+
fmt.Println(err)
71+
return err
72+
}
73+
err = listen(ws)
74+
if err != nil {
75+
fmt.Println(err)
76+
return err
77+
}
78+
return nil
79+
}
80+
81+
func listen(ws *models.WebsocketConnection) error {
82+
for {
83+
fmt.Println("Listening for Messages")
84+
_, messageBytes, err := ws.Connection.ReadMessage()
85+
if err != nil {
86+
if websocket.IsUnexpectedCloseError(err, websocket.CloseNormalClosure, websocket.CloseGoingAway, websocket.CloseAbnormalClosure) {
87+
fmt.Println("WS Connection ", ws.ID, " closing with error : ", err)
88+
return err
89+
}
90+
return nil
91+
}
92+
err = controllers.HandleMessage(ws, messageBytes)
93+
if err != nil {
94+
fmt.Println(err)
95+
return err
96+
}
97+
}
98+
}

0 commit comments

Comments
 (0)