[Code] be able to fallback to generic lang server if dedicated lang s… (#42317)

* [Code] be able to fallback to generic lang server if dedicated lang server is disabled or not installed
This commit is contained in:
Yulong 2019-08-01 09:21:12 +08:00 committed by GitHub
parent 7e46de5a02
commit 4c85ada320
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 78 additions and 51 deletions

View file

@ -38,4 +38,6 @@ export const CTAGS_SUPPORT_LANGS = [
'shell',
'sql',
'tcl',
'java',
'javascript',
];

View file

@ -64,7 +64,6 @@ export class StatusIndicatorComponent extends React.Component<Props, State> {
const { statusReport } = this.props;
let severity = Severity.NONE;
const children: any[] = [];
const addError = (error: RepoFileStatus | LangServerType) => {
// @ts-ignore
const s: any = REPO_FILE_STATUS_SEVERITY[error];

View file

@ -200,14 +200,14 @@ describe('lsp_incremental_indexer unit tests', () => {
// There are 5 MODIFIED items and 1 ADDED item. Only 1 file is in supported
// language. Each file with supported language has 1 file + 1 symbol + 1 reference.
// Total doc indexed should be 8 * 3 = 15,
// Total doc indexed should be 6 * 2 + 4 = 16,
// which can be fitted into a single batch index.
assert.strictEqual(bulkSpy.callCount, 2);
let total = 0;
for (let i = 0; i < bulkSpy.callCount; i++) {
total += bulkSpy.getCall(i).args[0].body.length;
}
assert.strictEqual(total, 8 * 2);
assert.strictEqual(total, 16);
// @ts-ignore
}).timeout(20000);

View file

@ -211,7 +211,7 @@ describe('lsp_service tests', () => {
assert.ok(workspaceFolderExists);
const controller = lspservice.controller;
// @ts-ignore
const languageServer = controller.languageServerMap.typescript;
const languageServer = controller.languageServerMap.typescript[0];
const realWorkspacePath = fs.realpathSync(workspacePath);
// @ts-ignore
@ -268,7 +268,7 @@ describe('lsp_service tests', () => {
await lspservice.shutdown();
}
// @ts-ignore
}).timeout(10000);
}).timeout(20000);
it('should update if a worktree is not the newest', async () => {
const lspservice = mockLspService();

View file

@ -19,10 +19,10 @@ export const LspServiceDefinition = {
},
languageSeverDef: {
request: {} as { lang: string },
response: {} as LanguageServerDefinition | null,
response: {} as LanguageServerDefinition[],
},
languageServerStatus: {
request: {} as { lang: string },
request: {} as { langName: string },
response: {} as LanguageServerStatus,
},
initializeState: {
@ -40,8 +40,8 @@ export const getLspServiceHandler = (
async languageSeverDef({ lang }) {
return lspService.getLanguageSeverDef(lang);
},
async languageServerStatus({ lang }) {
return await lspService.languageServerStatus(lang);
async languageServerStatus({ langName }) {
return lspService.languageServerStatus(langName);
},
async initializeState({ repoUri, revision }) {
return await lspService.initializeState(repoUri, revision);

View file

@ -46,7 +46,7 @@ export class LanguageServerController implements ILanguageServerHandler {
// a list of support language servers
private readonly languageServers: LanguageServerData[];
// a { lang -> server } map from above list
private readonly languageServerMap: { [lang: string]: LanguageServerData };
private readonly languageServerMap: { [lang: string]: LanguageServerData[] };
private log: Logger;
constructor(
@ -64,13 +64,22 @@ export class LanguageServerController implements ILanguageServerHandler {
maxWorkspace: options.maxWorkspace,
launcher: new def.launcher(this.targetHost, options, loggerFactory),
}));
const add2map = (
map: { [lang: string]: LanguageServerData[] },
lang: string,
ls: LanguageServerData
) => {
const arr = map[lang] || [];
arr.push(ls);
map[lang] = arr.sort((a, b) => b.definition.priority - a.definition.priority);
};
this.languageServerMap = this.languageServers.reduce(
(map, ls) => {
ls.languages.forEach(lang => (map[lang] = ls));
map[ls.definition.name] = ls;
ls.languages.forEach(lang => add2map(map, lang, ls));
map[ls.definition.name] = [ls];
return map;
},
{} as { [lang: string]: LanguageServerData }
{} as { [lang: string]: LanguageServerData[] }
);
}
@ -183,28 +192,24 @@ export class LanguageServerController implements ILanguageServerHandler {
}
}
public status(lang: string): LanguageServerStatus {
const ls = this.languageServerMap[lang];
if (ls) {
const status = this.installManager.status(ls.definition);
// installed, but is it running?
if (status === LanguageServerStatus.READY) {
if (ls.launcher.running) {
return LanguageServerStatus.RUNNING;
}
public status(def: LanguageServerDefinition): LanguageServerStatus {
const status = this.installManager.status(def);
// installed, but is it running?
if (status === LanguageServerStatus.READY) {
const ls = this.languageServers.find(d => d.definition === def);
if (ls && ls.launcher.running) {
return LanguageServerStatus.RUNNING;
}
return status;
} else {
return LanguageServerStatus.NOT_INSTALLED;
}
return status;
}
public getLanguageServerDef(lang: string): LanguageServerDefinition | null {
public getLanguageServerDef(lang: string): LanguageServerDefinition[] {
const data = this.languageServerMap[lang];
if (data) {
return data.definition;
return data.map(d => d.definition);
}
return null;
return [];
}
private async findOrCreateHandler(
@ -254,18 +259,18 @@ export class LanguageServerController implements ILanguageServerHandler {
}
private findLanguageServer(lang: string) {
const ls = this.languageServerMap[lang];
if (ls) {
if (
!this.options.lsp.detach &&
this.installManager.status(ls.definition) !== LanguageServerStatus.READY
) {
const lsArr = this.languageServerMap[lang];
if (lsArr) {
const ls = lsArr.find(
d => this.installManager.status(d.definition) !== LanguageServerStatus.NOT_INSTALLED
);
if (!this.options.lsp.detach && ls === undefined) {
throw new ResponseError(
LanguageServerNotInstalled,
`language server ${lang} not installed`
);
} else {
return ls;
return ls!;
}
} else {
throw new ResponseError(UnknownFileLanguage, `unsupported language ${lang}`);

View file

@ -21,6 +21,7 @@ export interface LanguageServerDefinition extends LanguageServer {
downloadUrl?: (version: string, devMode?: boolean) => string;
embedPath?: string;
installationPluginName?: string;
priority: number;
}
export const TYPESCRIPT: LanguageServerDefinition = {
@ -30,6 +31,7 @@ export const TYPESCRIPT: LanguageServerDefinition = {
launcher: TypescriptServerLauncher,
installationType: InstallationType.Embed,
embedPath: require.resolve('@elastic/javascript-typescript-langserver/lib/language-server.js'),
priority: 2,
};
export const JAVA: LanguageServerDefinition = {
name: 'Java',
@ -39,6 +41,7 @@ export const JAVA: LanguageServerDefinition = {
installationType: InstallationType.Plugin,
installationPluginName: 'java-langserver',
installationFolderName: 'jdt',
priority: 2,
downloadUrl: (version: string, devMode?: boolean) =>
devMode!
? `https://snapshots.elastic.co/downloads/java-langserver-plugins/java-langserver/java-langserver-${version}-SNAPSHOT-$OS.zip`
@ -51,6 +54,7 @@ export const GO: LanguageServerDefinition = {
launcher: GoServerLauncher,
installationType: InstallationType.Plugin,
installationPluginName: 'goLanguageServer',
priority: 2,
};
export const CTAGS: LanguageServerDefinition = {
name: 'Ctags',
@ -59,6 +63,7 @@ export const CTAGS: LanguageServerDefinition = {
launcher: CtagsLauncher,
installationType: InstallationType.Embed,
embedPath: require.resolve('@elastic/ctags-langserver/lib/cli.js'),
priority: 1,
};
export const LanguageServers: LanguageServerDefinition[] = [TYPESCRIPT, JAVA, CTAGS];
export const LanguageServersDeveloping: LanguageServerDefinition[] = [GO];

View file

@ -79,15 +79,21 @@ export class LspService {
}
public supportLanguage(lang: string) {
return this.controller.getLanguageServerDef(lang) !== null;
return this.controller.getLanguageServerDef(lang).length > 0;
}
public getLanguageSeverDef(lang: string) {
return this.controller.getLanguageServerDef(lang);
}
public languageServerStatus(lang: string): LanguageServerStatus {
return this.controller.status(lang);
public languageServerStatus(name: string): LanguageServerStatus {
const defs = this.controller.getLanguageServerDef(name);
if (defs.length > 0) {
const def = defs[0];
return this.controller.status(def);
} else {
return LanguageServerStatus.NOT_INSTALLED;
}
}
public async initializeState(repoUri: string, revision: string) {

View file

@ -18,7 +18,7 @@ export function installRoute(router: CodeServerRouter, codeServices: CodeService
const kibanaVersion = router.server.config().get('pkg.version') as string;
const status = (endpoint: Endpoint, def: LanguageServerDefinition) => ({
name: def.name,
status: lspService.languageServerStatus(endpoint, { lang: def.name }),
status: lspService.languageServerStatus(endpoint, { langName: def.name }),
version: def.version,
build: def.build,
languages: def.languages,

View file

@ -55,38 +55,48 @@ export function statusRoute(router: CodeServerRouter, codeServices: CodeServices
) {
if (blob.content) {
const lang: string = blob.lang!;
const def = await lspService.languageSeverDef(endpoint, { lang });
if (def === null) {
const defs = await lspService.languageSeverDef(endpoint, { lang });
if (defs.length === 0) {
report.fileStatus = RepoFileStatus.FILE_NOT_SUPPORTED;
} else {
return def;
return defs;
}
} else {
report.fileStatus = RepoFileStatus.FILE_IS_TOO_BIG;
}
return [];
}
async function handleLspStatus(
endpoint: Endpoint,
report: StatusReport,
def: LanguageServerDefinition,
defs: LanguageServerDefinition[],
repoUri: string,
revision: string
) {
report.langServerType = def === CTAGS ? LangServerType.GENERIC : LangServerType.DEDICATED;
const status = await lspService.languageServerStatus(endpoint, { lang: def.languages[0] });
if (status === LanguageServerStatus.NOT_INSTALLED) {
const dedicated = defs.find(d => d !== CTAGS);
const generic = defs.find(d => d === CTAGS);
report.langServerType = dedicated ? LangServerType.DEDICATED : LangServerType.GENERIC;
if (
dedicated &&
(await lspService.languageServerStatus(endpoint, { langName: dedicated.name })) ===
LanguageServerStatus.NOT_INSTALLED
) {
report.langServerStatus = RepoFileStatus.LANG_SERVER_NOT_INSTALLED;
if (generic) {
// dedicated lang server not installed, fallback to generic
report.langServerType = LangServerType.GENERIC;
}
} else {
const def = dedicated || generic;
const state = await lspService.initializeState(endpoint, { repoUri, revision });
const initState = state[def.name];
const initState = state[def!.name];
report.langServerStatus =
initState === WorkspaceStatus.Initialized
? RepoFileStatus.LANG_SERVER_INITIALIZED
: RepoFileStatus.LANG_SERVER_IS_INITIALIZING;
}
}
router.route({
path: '/api/code/repo/{uri*3}/status/{ref}/{path*}',
method: 'GET',
@ -113,9 +123,9 @@ export function statusRoute(router: CodeServerRouter, codeServices: CodeServices
});
// text file
if (!blob.isBinary) {
const def = await handleFileStatus(endpoint, report, blob);
if (def) {
await handleLspStatus(endpoint, report, def, uri, ref);
const defs = await handleFileStatus(endpoint, report, blob);
if (defs.length > 0) {
await handleLspStatus(endpoint, report, defs, uri, ref);
}
}
} catch (e) {