[Code] use status api to decide whether a lsp request should be sent (#41310) (#41445)

This commit is contained in:
Yulong 2019-07-19 01:03:19 +08:00 committed by Mengwei Ding
parent dc24720f62
commit 1616ba5ffc
10 changed files with 175 additions and 73 deletions

View file

@ -6,6 +6,7 @@
export enum RepoFileStatus {
LANG_SERVER_IS_INITIALIZING = 'Language server is initializing.',
LANG_SERVER_INITIALIZED = 'Language server initialized.',
INDEXING = 'Indexing in progress',
FILE_NOT_SUPPORTED = 'Current file is not of a supported language.',
REVISION_NOT_INDEXED = 'Current revision is not indexed.',
@ -60,8 +61,9 @@ export const REPO_FILE_STATUS_SEVERITY = {
export interface StatusReport {
repoStatus?: RepoFileStatus.INDEXING | RepoFileStatus.REVISION_NOT_INDEXED;
fileStatus?: RepoFileStatus.FILE_NOT_SUPPORTED | RepoFileStatus.FILE_IS_TOO_BIG;
langServerType: LangServerType;
langServerType?: LangServerType;
langServerStatus?:
| RepoFileStatus.LANG_SERVER_IS_INITIALIZING
| RepoFileStatus.LANG_SERVER_NOT_INSTALLED;
| RepoFileStatus.LANG_SERVER_NOT_INSTALLED
| RepoFileStatus.LANG_SERVER_INITIALIZED;
}

View file

@ -70,3 +70,8 @@ export const FetchRepoFileStatusSuccess = createAction<{
statusReport: StatusReport;
}>('FETCH REPO FILE STATUS SUCCESS');
export const FetchRepoFileStatusFailed = createAction<any>('FETCH REPO FILE STATUS FAILED');
export const StatusChanged = createAction<{
prevStatus: StatusReport;
currentStatus: StatusReport;
}>('STATUS CHANGED');

View file

@ -30,7 +30,7 @@ import {
currentTreeSelector,
hasMoreCommitsSelector,
repoUriSelector,
statusSelector,
repoStatusSelector,
} from '../../selectors';
import { encodeRevisionString } from '../../../common/uri_util';
import { history } from '../../utils/url';
@ -404,7 +404,7 @@ const mapStateToProps = (state: RootState) => ({
branches: state.revision.branches,
hasMoreCommits: hasMoreCommitsSelector(state),
loadingCommits: state.revision.loadingCommits,
repoStatus: statusSelector(state, repoUriSelector(state)),
repoStatus: repoStatusSelector(state, repoUriSelector(state)),
searchOptions: state.search.searchOptions,
query: state.search.query,
currentRepository: state.repository.repository,

View file

@ -75,12 +75,12 @@ export class StatusIndicatorComponent extends React.Component<Props, State> {
if (fix !== undefined) {
const fixUrl = this.fixUrl(fix);
children.push(
<p>
<p key={`${error}_key`}>
{error} You can {fixUrl}.
</p>
);
} else {
children.push(<p>{error}</p>);
children.push(<p key={`${error}_key`}>{error}</p>);
}
}
};
@ -99,7 +99,13 @@ export class StatusIndicatorComponent extends React.Component<Props, State> {
}
}
const svg = svgs[severity];
const icon = <EuiButtonIcon iconType={svg} onClick={this.openPopover.bind(this)} />;
const icon = (
<EuiButtonIcon
aria-label="status_indicator"
iconType={svg}
onClick={this.openPopover.bind(this)}
/>
);
if (children.length === 0) {
return <div />;
}

View file

@ -7,6 +7,10 @@
import { Hover } from 'vscode-languageserver-types';
import { LspRestClient, TextDocumentMethods } from '../../../common/lsp_client';
import { AsyncTask, Computer } from '../computer';
import { store } from '../../stores/index';
import { statusSelector } from '../../selectors';
import { LangServerType, RepoFileStatus } from '../../../common/repo_file_status';
import { ServerNotInitialized } from '../../../common/lsp_error_codes';
export const LOADING = 'loading';
@ -26,29 +30,68 @@ export class HoverComputer implements Computer<Hover> {
}
public compute(): AsyncTask<Hover> {
return this.lspMethods.hover.asyncTask({
position: {
line: this.range!.startLineNumber - 1,
character: this.range!.startColumn - 1,
const status = statusSelector(store.getState());
if (
status &&
status.langServerType !== LangServerType.NONE &&
status.fileStatus !== RepoFileStatus.FILE_NOT_SUPPORTED &&
status.fileStatus !== RepoFileStatus.FILE_IS_TOO_BIG
) {
if (status.langServerStatus === RepoFileStatus.LANG_SERVER_IS_INITIALIZING) {
return this.initializing();
}
return this.lspMethods.hover.asyncTask({
position: {
line: this.range!.startLineNumber - 1,
character: this.range!.startColumn - 1,
},
textDocument: {
uri: this.uri!,
},
});
}
return this.empty();
}
private empty(): AsyncTask<Hover> {
const empty = {
range: this.lspRange(),
contents: [],
};
return {
cancel(): void {},
promise(): Promise<Hover> {
return Promise.resolve(empty);
},
textDocument: {
uri: this.uri!,
};
}
private initializing(): AsyncTask<Hover> {
return {
cancel(): void {},
promise(): Promise<Hover> {
return Promise.reject({ code: ServerNotInitialized });
},
});
};
}
private lspRange() {
return {
start: {
line: this.range.startLineNumber - 1,
character: this.range.startColumn - 1,
},
end: {
line: this.range.endLineNumber - 1,
character: this.range.endColumn - 1,
},
};
}
public loadingMessage(): Hover {
return {
range: {
start: {
line: this.range.startLineNumber - 1,
character: this.range.startColumn - 1,
},
end: {
line: this.range.endLineNumber - 1,
character: this.range.endColumn - 1,
},
},
range: this.lspRange(),
contents: LOADING,
};
}

View file

@ -27,6 +27,7 @@ import {
StatusSuccessPayload,
RepoStatus,
RepoState,
FetchRepoFileStatus,
} from '../actions';
import { StatusReport } from '../../common/repo_file_status';
@ -223,6 +224,10 @@ export const status = handleActions<StatusState, StatusPayload>(
draft.repoFileStatus = statusReport;
draft.currentStatusPath = path;
}),
[String(FetchRepoFileStatus)]: (state: StatusState, action: Action<any>) =>
produce<StatusState>(state, (draft: StatusState) => {
draft.currentStatusPath = action.payload;
}),
},
initialState
);

View file

@ -8,26 +8,29 @@ import { Action } from 'redux-actions';
import { call, put, select, takeEvery, takeLatest } from 'redux-saga/effects';
import { kfetch } from 'ui/kfetch';
import { isEqual } from 'lodash';
import { delay } from 'redux-saga';
import { RepositoryUri, WorkerReservedProgress } from '../../model';
import {
deleteRepoFinished,
FetchFilePayload,
FetchRepoFileStatus,
FetchRepoFileStatusFailed,
FetchRepoFileStatusSuccess,
Match,
routeChange,
updateCloneProgress,
updateDeleteProgress,
pollRepoCloneStatusStop,
pollRepoDeleteStatusStop,
pollRepoIndexStatusStop,
FetchRepoFileStatus,
FetchRepoFileStatusSuccess,
FetchRepoFileStatusFailed,
FetchFilePayload,
routeChange,
StatusChanged,
updateCloneProgress,
updateDeleteProgress,
} from '../actions';
import * as ROUTES from '../components/routes';
import { RootState } from '../reducers';
import { mainRoutePattern } from './patterns';
import { StatusReport } from '../../common/repo_file_status';
import { RepoFileStatus, StatusReport } from '../../common/repo_file_status';
import { statusSelector } from '../selectors';
const matchSelector = (state: RootState) => state.route.match;
@ -83,20 +86,45 @@ function* handleMainRouteChange(action: Action<Match>) {
const newStatusPath: FetchFilePayload = { uri, revision, path };
const currentStatusPath = yield select((state: RootState) => state.status.currentStatusPath);
if (!isEqual(newStatusPath, currentStatusPath)) {
yield call(fetchStatus, newStatusPath);
yield call(startPollingStatus, newStatusPath);
}
}
const STATUS_POLLING_FREQ_HIGH_MS = 3000;
const STATUS_POLLING_FREQ_LOW_MS = 10000;
function* startPollingStatus(location: FetchFilePayload) {
yield put(FetchRepoFileStatus(location));
let currentStatusPath = yield select((state: RootState) => state.status.currentStatusPath);
while (isEqual(location, currentStatusPath)) {
const previousStatus: StatusReport = yield select(statusSelector);
const newStatus: StatusReport = yield call(fetchStatus, location);
yield call(compareStatus, previousStatus, newStatus);
const delayMs =
newStatus.langServerStatus === RepoFileStatus.LANG_SERVER_IS_INITIALIZING
? STATUS_POLLING_FREQ_HIGH_MS
: STATUS_POLLING_FREQ_LOW_MS;
yield call(delay, delayMs);
currentStatusPath = yield select((state: RootState) => state.status.currentStatusPath);
}
}
function* compareStatus(prevStatus: StatusReport, currentStatus: StatusReport) {
if (!isEqual(prevStatus, currentStatus)) {
yield put(StatusChanged({ prevStatus, currentStatus }));
}
}
function* fetchStatus(location: FetchFilePayload) {
yield put(FetchRepoFileStatus(location));
try {
const newStatus = yield call(requestStatus, location);
const newStatus: StatusReport = yield call(requestStatus, location);
yield put(
FetchRepoFileStatusSuccess({
statusReport: newStatus as StatusReport,
statusReport: newStatus,
path: location,
})
);
return newStatus;
} catch (e) {
yield put(FetchRepoFileStatusFailed(e));
}
@ -104,8 +132,12 @@ function* fetchStatus(location: FetchFilePayload) {
function requestStatus(location: FetchFilePayload) {
const { uri, revision, path } = location;
const pathname = path
? `/api/code/repo/${uri}/status/${revision}/${path}`
: `/api/code/repo/${uri}/status/${revision}`;
return kfetch({
pathname: `/api/code/repo/${uri}/status/${revision}/${path}`,
pathname,
method: 'GET',
});
}

View file

@ -5,17 +5,20 @@
*/
import { Action } from 'redux-actions';
import { delay } from 'redux-saga';
import { call, put, takeEvery, cancel, take, fork } from 'redux-saga/effects';
import { call, put, select, takeEvery } from 'redux-saga/effects';
import { SymbolInformation } from 'vscode-languageserver-types/lib/esm/main';
import { LspRestClient, TextDocumentMethods } from '../../common/lsp_client';
import { loadStructure, loadStructureFailed, loadStructureSuccess } from '../actions';
import { ServerNotInitialized } from '../../common/lsp_error_codes';
import { languageServerInitializing } from '../actions/language_server';
import {
loadStructure,
loadStructureFailed,
loadStructureSuccess,
StatusChanged,
} from '../actions';
import { SymbolWithMembers } from '../actions/structure';
import { matchContainerName } from '../utils/symbol_utils';
const STRUCTURE_TREE_POLLING_INTERVAL_SEC = 3;
import { RepoFileStatus, StatusReport } from '../../common/repo_file_status';
import { RootState } from '../reducers';
import { toCanonicalUrl } from '../../common/uri_util';
type Container = SymbolWithMembers | undefined;
@ -103,33 +106,39 @@ function requestStructure(uri?: string) {
});
}
function* beginPollingSymbols(action: Action<string>) {
try {
const pollingTaskId = yield fork(pollingSaga, action);
yield take([String(loadStructureSuccess), String(loadStructureFailed)]);
yield cancel(pollingTaskId);
} catch (err) {
yield put(loadStructureFailed(err));
function* statusChanged(action: Action<any>) {
const {
prevStatus,
currentStatus,
}: { prevStatus: StatusReport; currentStatus: StatusReport } = action.payload;
if (
prevStatus &&
prevStatus.langServerStatus === RepoFileStatus.LANG_SERVER_IS_INITIALIZING &&
currentStatus.langServerStatus !== RepoFileStatus.LANG_SERVER_IS_INITIALIZING
) {
const { revision, uri, path } = yield select(
(state: RootState) => state.status.currentStatusPath
);
const u = toCanonicalUrl({
repoUri: uri,
file: path,
revision,
});
yield call(fetchSymbols, loadStructure(u));
}
}
export function* watchLoadStructure() {
yield takeEvery(String(loadStructure), beginPollingSymbols);
yield takeEvery(String(loadStructure), fetchSymbols);
yield takeEvery(String(StatusChanged), statusChanged);
}
function* pollingSaga(action: Action<string>) {
while (true) {
try {
const data = yield call(requestStructure, `git:/${action.payload}`);
const structureTree = generateStructureTree(data);
yield put(loadStructureSuccess({ path: action.payload!, data, structureTree }));
} catch (e) {
if (e.code && e.code === ServerNotInitialized) {
yield put(languageServerInitializing());
yield delay(STRUCTURE_TREE_POLLING_INTERVAL_SEC * 1000);
} else {
yield put(loadStructureFailed(e));
}
}
function* fetchSymbols(action: Action<string>) {
try {
const data = yield call(requestStructure, `git:/${action.payload}`);
const structureTree = generateStructureTree(data);
yield put(loadStructureSuccess({ path: action.payload!, data, structureTree }));
} catch (e) {
yield put(loadStructureFailed(e));
}
}

View file

@ -38,7 +38,7 @@ export const repoUriSelector = (state: RootState) => {
export const routeSelector = (state: RootState) => state.route.match;
export const statusSelector = (state: RootState, repoUri: RepositoryUri) => {
export const repoStatusSelector = (state: RootState, repoUri: RepositoryUri) => {
return state.status.status[repoUri];
};
@ -101,3 +101,5 @@ export const repoScopeSelector = (state: RootState) => state.search.searchOption
export const urlQueryStringSelector = (state: RootState) => state.route.match.location.search;
export const previousMatchSelector = (state: RootState) => state.route.previousMatch;
export const statusSelector = (state: RootState) => state.status.repoFileStatus;

View file

@ -72,9 +72,10 @@ export function statusRoute(
} else {
const state = await lspService.initializeState(repoUri, revision);
const initState = state[def.name];
if (initState !== WorkspaceStatus.Initialized) {
report.langServerStatus = RepoFileStatus.LANG_SERVER_IS_INITIALIZING;
}
report.langServerStatus =
initState === WorkspaceStatus.Initialized
? RepoFileStatus.LANG_SERVER_INITIALIZED
: RepoFileStatus.LANG_SERVER_IS_INITIALIZING;
}
}
@ -83,9 +84,7 @@ export function statusRoute(
method: 'GET',
async handler(req: hapi.Request) {
const { uri, path, ref } = req.params;
const report: StatusReport = {
langServerType: LangServerType.NONE,
};
const report: StatusReport = {};
const repoObjectClient = new RepositoryObjectClient(new EsClientWithRequest(req));
try {
// Check if the repository already exists
@ -94,7 +93,6 @@ export function statusRoute(
return Boom.notFound(`repo ${uri} not found`);
}
await handleRepoStatus(report, uri, ref, repoObjectClient);
if (path) {
try {
try {