Skip to content

Commit

Permalink
Merge pull request #126 from mkslanc/lsp-ai
Browse files Browse the repository at this point in the history
Fix missing version on mode change
  • Loading branch information
mkslanc committed Jun 18, 2024
2 parents 162da74 + a4c4d37 commit 0959dd6
Show file tree
Hide file tree
Showing 15 changed files with 187 additions and 32 deletions.
14 changes: 7 additions & 7 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "ace-linters-root",
"version": "1.2.2",
"version": "1.2.3",
"scripts": {
"build:ace-linters": "cd packages/ace-linters && npm run build",
"build:ace-sql-linter": "cd packages/ace-sql-linter && npm run build",
Expand All @@ -10,7 +10,7 @@
"start-dev": "webpack-dev-server"
},
"dependencies": {
"ace-code": "^1.33.2",
"ace-code": "^1.35.0",
"jsonc-parser": "latest",
"typescript": "^5.3.3"
},
Expand Down
2 changes: 1 addition & 1 deletion packages/ace-linters/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "ace-linters",
"author": "Azat Alimov <mkslanc@gmail.com>",
"version": "1.2.2",
"version": "1.2.3",
"scripts": {
"clean": "rimraf build",
"prebuild": "node prebuild.js",
Expand Down
6 changes: 3 additions & 3 deletions packages/ace-linters/src/language-provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -331,7 +331,7 @@ export class LanguageProvider {


$registerCompleters(editor: Ace.Editor) {
let completer = {
let completer: Ace.Completer = {
getCompletions: async (editor, session, pos, prefix, callback) => {
this.$getSessionLanguageProvider(session).$sendDeltaQueue(() => {
this.doComplete(editor, session, (completions) => {
Expand Down Expand Up @@ -510,8 +510,8 @@ class SessionLanguageProvider {
}

this.session.setSemanticTokens(undefined); //clear all semantic tokens

this.$messageController.changeMode(this.fileName, this.session.getValue(), this.$mode, this.setServerCapabilities);
let newVersion = this.session.doc["version"]++;
this.$messageController.changeMode(this.fileName, this.session.getValue(), newVersion, this.$mode, this.setServerCapabilities);
};

private setServerCapabilities = (capabilities: { [serviceName: string]: lsp.ServerCapabilities }) => {
Expand Down
4 changes: 2 additions & 2 deletions packages/ace-linters/src/message-controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,8 +70,8 @@ export class MessageController extends EventEmitter implements IMessageControlle
this.postMessage(message, callback)
}

changeMode(sessionId: string, value: string, mode: string, callback?: (capabilities) => void) {
this.postMessage(new ChangeModeMessage(sessionId, value, mode), callback);
changeMode(sessionId: string, value: string, version: number, mode: string, callback?: (capabilities) => void) {
this.postMessage(new ChangeModeMessage(sessionId, value, version, mode), callback);
}

changeOptions(sessionId: string, options: ServiceOptions, callback?: () => void, merge = false) {
Expand Down
4 changes: 3 additions & 1 deletion packages/ace-linters/src/message-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,11 +107,13 @@ export class ChangeModeMessage extends BaseMessage {
type: MessageType.changeMode = MessageType.changeMode;
mode: string;
value: string;
version: number;

constructor(sessionId: string, value: string, mode: string) {
constructor(sessionId: string, value: string, version: number, mode: string) {
super(sessionId);
this.value = value;
this.mode = mode;
this.version = version;
}
}

Expand Down
8 changes: 2 additions & 6 deletions packages/ace-linters/src/services/language-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@ export class LanguageClient extends BaseService implements LanguageService {
}

enqueueIfNotConnected(callback: () => void) {
if (!this.isConnected) {
if (!this.isConnected || !this.isInitialized) {
this.requestsQueue.push(callback);
} else {
callback();
Expand Down Expand Up @@ -341,14 +341,10 @@ export class LanguageClient extends BaseService implements LanguageService {

setGlobalOptions(options: ServiceOptions): void {
super.setGlobalOptions(options);
if (!this.isConnected) {
this.requestsQueue.push(() => this.setGlobalOptions(options));
return;
}
const configChanges: lsp.DidChangeConfigurationParams = {
settings: options
};
this.connection.sendNotification('workspace/didChangeConfiguration', configChanges);
this.enqueueIfNotConnected(() => this.connection.sendNotification('workspace/didChangeConfiguration', configChanges));
}

async findDocumentHighlights(document: lsp.TextDocumentIdentifier, position: lsp.Position) {
Expand Down
14 changes: 9 additions & 5 deletions packages/ace-linters/src/type-converters/lsp/lsp-converters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,8 +75,12 @@ export function toCompletion(item: CompletionItem) {
let itemKind = item.kind;
let kind = itemKind ? Object.keys(CompletionItemKind)[Object.values(CompletionItemKind).indexOf(itemKind)] : undefined;
let text = item.textEdit?.newText ?? item.insertText ?? item.label;
// filtering would happen on ace editor side
// TODO: if filtering and sorting are on server side, we should disable FilteredList in ace completer
text = item.filterText && !text.startsWith(item.filterText) ? item.filterText + text : text;

let command = (item.command?.command == "editor.action.triggerSuggest") ? "startAutocomplete" : undefined;
let range = item.textEdit ? getTextEditRange(item.textEdit) : undefined;
let range = item.textEdit ? getTextEditRange(item.textEdit, item.filterText) : undefined;
let completion = {
meta: kind,
caption: item.label,
Expand Down Expand Up @@ -159,13 +163,13 @@ export function toCompletionItem(completion: Ace.Completion): CompletionItem {
return completionItem;
}

export function getTextEditRange(textEdit: TextEdit | InsertReplaceEdit): AceRangeData {
if (textEdit.hasOwnProperty("insert") && textEdit.hasOwnProperty("replace")) {
textEdit = textEdit as InsertReplaceEdit;
export function getTextEditRange(textEdit: TextEdit | InsertReplaceEdit, filterText?: string): AceRangeData {
const filterLength = filterText ? filterText.length : 0;
if ("insert" in textEdit && "replace" in textEdit) { //TODO: maybe we need to compensate here too
let mergedRanges = mergeRanges([toRange(textEdit.insert), toRange(textEdit.replace)]);
return mergedRanges[0];
} else {
textEdit = textEdit as TextEdit;
textEdit.range.start.character -= filterLength;
return toRange(textEdit.range);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ export interface IMessageController {

change(sessionId: string, deltas: lsp.TextDocumentContentChangeEvent[], document: Ace.Document, callback?: () => void): void;

changeMode(sessionId: string, value: string, mode: string, callback?: (capabilities: { [serviceName: string]: lsp.ServerCapabilities }) => void);
changeMode(sessionId: string, value: string, version: number, mode: string, callback?: (capabilities: { [serviceName: string]: lsp.ServerCapabilities }) => void);

changeOptions(sessionId: string, options: ServiceOptions, callback?: () => void);

Expand Down
2 changes: 1 addition & 1 deletion packages/ace-linters/types/message-controller.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export declare class MessageController extends EventEmitter implements IMessageC
format(sessionId: string, range: lsp.Range, format: lsp.FormattingOptions, callback?: (edits: lsp.TextEdit[]) => void): void;
doHover(sessionId: string, position: lsp.Position, callback?: (hover: lsp.Hover[]) => void): void;
change(sessionId: string, deltas: any, document: Ace.Document, callback?: () => void): void;
changeMode(sessionId: string, value: string, mode: string, callback?: (capabilities: any) => void): void;
changeMode(sessionId: string, value: string, version: number, mode: string, callback?: (capabilities: any) => void): void;
changeOptions(sessionId: string, options: ServiceOptions, callback?: () => void, merge?: boolean): void;
closeDocument(sessionId: string, callback?: () => void): void;
dispose(callback: () => void): void;
Expand Down
3 changes: 2 additions & 1 deletion packages/ace-linters/types/message-types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,8 @@ export declare class ChangeModeMessage extends BaseMessage {
type: MessageType.changeMode;
mode: string;
value: string;
constructor(sessionId: string, value: string, mode: string);
version: number;
constructor(sessionId: string, value: string, version: number, mode: string);
}
export declare class ChangeOptionsMessage extends BaseMessage {
type: MessageType.changeOptions;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export declare function toCompletion(item: CompletionItem): {
export declare function toCompletions(completions: CompletionService[]): Ace.Completion[];
export declare function toResolvedCompletion(completion: Ace.Completion, item: CompletionItem): Ace.Completion;
export declare function toCompletionItem(completion: Ace.Completion): CompletionItem;
export declare function getTextEditRange(textEdit: TextEdit | InsertReplaceEdit): AceRangeData;
export declare function getTextEditRange(textEdit: TextEdit | InsertReplaceEdit, filterText?: string): AceRangeData;
export declare function toTooltip(hover: Hover[] | undefined): Tooltip | undefined;
export declare function fromSignatureHelp(signatureHelp: SignatureHelp[] | undefined): Tooltip | undefined;
export declare function fromMarkupContent(content?: string | MarkupContent): string | undefined;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export interface IMessageController {
format(sessionId: string, range: lsp.Range, format: lsp.FormattingOptions, callback?: (edits: lsp.TextEdit[]) => void): any;
doHover(sessionId: string, position: lsp.Position, callback?: (hover: lsp.Hover[]) => void): any;
change(sessionId: string, deltas: lsp.TextDocumentContentChangeEvent[], document: Ace.Document, callback?: () => void): void;
changeMode(sessionId: string, value: string, mode: string, callback?: (capabilities: {
changeMode(sessionId: string, value: string, version: number, mode: string, callback?: (capabilities: {
[serviceName: string]: lsp.ServerCapabilities;
}) => void): any;
changeOptions(sessionId: string, options: ServiceOptions, callback?: () => void): any;
Expand Down
151 changes: 151 additions & 0 deletions packages/demo/websockets-lsp-ai/client.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
import "ace-code/esm-resolver";
import {AceLanguageClient} from "ace-linters/build/ace-language-client";
import {addFormatCommand, createEditorWithLSP} from "../utils";
import {jsContent} from "../docs-example/javascript-example";
import {LanguageClientConfig} from "ace-linters/types/types/language-service";
import {Ace} from "ace-code";
import {Autocomplete} from "ace-code/src/autocomplete";
import "ace-code/src/ext/inline_autocomplete";

let modes = [
{name: "javascript", mode: "ace/mode/javascript", content: jsContent},
]

const defaultGenerationConfiguration = {
"model": "model1",
"parameters": {
"max_tokens": 128,
"max_context": 1024,
"messages": [
{
"role": "system",
"content": "Instructions:\n- You are an AI programming assistant.\n- Given a piece of code with the cursor location marked by \"<CURSOR>\", replace \"<CURSOR>\" with the correct code or comment.\n- First, think step-by-step.\n- Describe your plan for what to build in pseudocode, written out in great detail.\n- Then output the code replacing the \"<CURSOR>\".\n- Ensure that your completion fits within the language context of the provided code snippet.\n\nRules:\n- Only respond with code or comments.\n- Only replace \"<CURSOR>\"; do not include any previously written code.\n- Never include \"<CURSOR>\" in your response.\n- If the cursor is within a comment, complete the comment meaningfully.\n- Handle ambiguous cases by providing the most contextually appropriate completion.\n- Be consistent with your responses."
},
{
"role": "user",
"content": "def greet(name):\n print(f\"Hello, {<CURSOR>}\")"
},
{
"role": "assistant",
"content": "name"
},
{
"role": "user",
"content": "function sum(a, b) {\n return a + <CURSOR>;\n}"
},
{
"role": "assistant",
"content": "b"
},
{
"role": "user",
"content": "fn multiply(a: i32, b: i32) -> i32 {\n a * <CURSOR>\n}"
},
{
"role": "assistant",
"content": "b"
},
{
"role": "user",
"content": "# <CURSOR>\ndef add(a, b):\n return a + b"
},
{
"role": "assistant",
"content": "Adds two numbers"
},
{
"role": "user",
"content": "# This function checks if a number is even\n<CURSOR>"
},
{
"role": "assistant",
"content": "def is_even(n):\n return n % 2 == 0"
},
{
"role": "user",
"content": "public class HelloWorld {\n public static void main(String[] args) {\n System.out.println(\"Hello, <CURSOR>\");\n }\n}"
},
{
"role": "assistant",
"content": "world"
},
{
"role": "user",
"content": "try:\n # Trying to open a file\n file = open(\"example.txt\", \"r\")\n # <CURSOR>\nfinally:\n file.close()"
},
{
"role": "assistant",
"content": "content = file.read()"
},
{
"role": "user",
"content": "#include <iostream>\nusing namespace std;\n\nint main() {\n int a = 5, b = 10;\n cout << \"Sum: \" << (a + <CURSOR>) << endl;\n return 0;\n}"
},
{
"role": "assistant",
"content": "b"
},
{
"role": "user",
"content": "<!DOCTYPE html>\n<html>\n<head>\n <title>My Page</title>\n</head>\n<body>\n <h1>Welcome to My Page</h1>\n <p>This is a sample page with a list of items:</p>\n <ul>\n <li>Item 1</li>\n <li>Item 2</li>\n <li><CURSOR></li>\n </ul>\n</body>\n</html>"
},
{
"role": "assistant",
"content": "Item 3"
},
{
"role": "user",
"content": "{CODE}"
}
]
}
}
// you will need OPENAI_API_KEY in env or use "auth_token" instead of "auth_token_env_var_name"
const defaultServerConfiguration =
{
"memory": {
"file_store": {}
},
"models": {
"model1": {
"type": "open_ai",
"chat_endpoint": "https://api.openai.com/v1/chat/completions",
"model": "gpt-3.5-turbo-0125",
"auth_token_env_var_name": "OPENAI_API_KEY"
}
},
"completion": {
...defaultGenerationConfiguration
}
}
const serverData: LanguageClientConfig = {
module: () => import("ace-linters/build/language-client"),
modes: "javascript",
type: "socket",
socket: new WebSocket("ws://localhost:3030/lsp-ai"),
initializationOptions: defaultServerConfiguration
}


let languageProvider = AceLanguageClient.for(serverData);

let editor = createEditorWithLSP(modes[0], 0, languageProvider);
//@ts-expect-error this should be added to ace declaration
editor.setOption("enableInlineAutocompletion", true);
editor.setOption("liveAutocompletionDelay", 500);

enableInnerAutocomplete(editor);

function enableInnerAutocomplete(editor: Ace.Editor) {
let originalAutocompleteCommand = editor.commands.byName.startAutocomplete.exec;
editor.commands.byName.startAutocomplete.exec = function (editor) {
var autocomplete = Autocomplete.for(editor);
autocomplete.getCompletionProvider().ignoreCaption = true;
autocomplete.inlineEnabled = true;
autocomplete.ignoreCaption = true;
// @ts-ignore
originalAutocompleteCommand(...arguments);
}
}

addFormatCommand(languageProvider);
1 change: 1 addition & 0 deletions webpack.config.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 0959dd6

Please sign in to comment.