copilot的进口函数

咱们将activate办法格式化如下:

asyncfunctionactivate(context){
//创立并标记为已发送的遥测数据
letactivationTelemetry=TelemetryData.createAndMarkAsIssued();
//创立扩展上下文,并等待其完结
letctx=awaitcreateExtensionContext(context);
//注册状况栏,并将CopilotRepositoryControlManager增加到上下文中
registerStatusBar(ctx,outputChannel);
ctx.set(CopilotRepositoryControlManager,newCopilotRepositoryControlManager(ctx));
//注册确诊命令
registerDiagnosticCommands(ctx);
//注册带有遥测的命令
registerCommandWithTelemetry(ctx,CMDSignIn,()=>getSession(ctx,!0));
//将CodeReference增加到订阅中
context.subscriptions.push(newCodeReference(ctx).register());
//将onDeactivate增加到订阅中
context.subscriptions.push(onDeactivate(ctx));
//界说一个异步函数tryActivation
lettryActivation=__name(async()=>{
letstatusBar=ctx.get(StatusReporter);
//设置进展,并允许一次性登录
statusBar.setProgress();
permitOneSignIn();
//界说一个处理过错的函数
letrejectionHandler=__name((error,allowRetry=!0)=>{
letreason=error.message||error;
//记载过错,并停用遥测
telemetryError(ctx,"activationFailed",TelemetryData.createAndMarkAsIssued({
reason:reason
}));
ctx.get(TelemetryReporters).deactivate();
//设置过错音讯,并允许重试
letmessage=reason==="GitHubLoginFailed"?SESSION_LOGIN_MESSAGE:`Extensionactivationfailed:"${reason}"`;
statusBar.setError(message,allowRetry?tryActivation:void0);
//记载过错,并将github.copilot.activated上下文设置为false
logger.error(ctx,message);
ja.commands.executeCommand("setContext","github.copilot.activated",!1);
},"rejectionHandler");
//检查Node.js版别是否受支持
letnodeVersionError=errorMessageForUnsupportedNodeVersion();
if(nodeVersionError){
rejectionHandler(nodeVersionError,!1);
return;
}
//获取Copilottoken并等待其完结
ctx.get(CopilotTokenManager).getCopilotToken(ctx).then(()=>{
//强制设置为正常状况,并将github.copilot.activated上下文设置为true
statusBar.forceNormal();
ja.commands.executeCommand("setContext","github.copilot.activated",!0);
//注册面板支持,注册GhostText支持,并将文档跟踪器和光标跟踪器增加到订阅中
registerPanelSupport(ctx);
registerGhostTextSupport(ctx);
context.subscriptions.push(registerDocumentTracker(ctx));
context.subscriptions.push(registerCursorTracker(ctx));
//增加事情处理器,当活动编辑器改动时提取仓库信息,当打开文档时预热语言检测缓存,当装备改动时调用onDidChangeConfigurationHandler
context.subscriptions.push(ja.window.onDidChangeActiveTextEditor(e=>e&&extractRepoInfoInBackground(ctx,e.document.uri)));
context.subscriptions.push(ja.workspace.onDidOpenTextDocument(doc=>primeLanguageDetectionCache(ctx,doc)));
context.subscriptions.push(ja.workspace.onDidChangeConfiguration(e=>onDidChangeConfigurationHandler(e,ctx)));
//检查扩展形式是否为开发形式
letisDevMode=context.extensionMode===ja.ExtensionMode.Development;
//初始化,假如不是开发形式,则发动线程,并发送激活遥测
init(ctx,!isDevMode,newLogger(1,"promptlibproxy"));
!isDevMode&&ctx.get(hy.SnippetOrchestrator).startThreading();
telemetry(ctx,"extension.activate",activationTelemetry);
//假如有活动的文本编辑器,则更新其内容
ja.window?.activeTextEditor&&ctx.get(CopilotRepositoryControlManager).evaluate(ja.window.activeTextEditor.document?.uri,ja.window.activeTextEditor.document.getText(),"UPDATE");
}).catch(ex=>{
//假如发生过错,则调用rejectionHandler
rejectionHandler(ex);
});
},"tryActivation");
//增加事情处理器,当会话改动时调用onDidChangeSessionsHandler
ja.authentication.onDidChangeSessions(asyncevent=>{
awaitonDidChangeSessionsHandler(event,ctx);
});
//发动VSCode装置办理器
newVsCodeInstallationManager().startup(ctx);
//等待tryActivation完结
awaittryActivation();
//回来CopilotExtensionApi的新实例
returnnewCopilotExtensionApi(ctx);
}

在进口函数中,涉及到了几个组件:

  • TelemetryData,负责创立上报数据。
  • createExtensionContext ,负责处理生成Context。

关于Context的初始化

asyncfunctioncreateExtensionContext(extensionContext){
//创立一个出产环境的上下文,并设置日志方针为控制台和输出通道
letctx=createProductionContext(newVSCodeConfigProvider()),
logTarget=newMultiLog([newConsoleLog(console),newOutputChannelLog(outputChannel)]);
ctx.forceSet(LogTarget,logTarget);
ctx.set(EditorAndPluginInfo,newVSCodeEditorInfo());
initProxyEnvironment(ctx.get(Fetcher),process.env);
ctx.set(NotificationSender,newExtensionNotificationSender());
ctx.set(EditorSession,newEditorSession(vscode.env.sessionId,vscode.env.machineId));
ctx.set(Extension,newExtension(extensionContext));
ctx.set(EditorExperimentFilters,newVSCodeEditorExperimentFilters());
setupExperimentationService(ctx);
ctx.set(SymbolDefinitionProvider,newExtensionSymbolDefinitionProvider());
ctx.set(CopilotExtensionStatus,newCopilotExtensionStatus());
//依据扩展形式(测验或出产)设置不同的服务和装备
if(extensionContext.extensionMode===vscode.ExtensionMode.Test){
ctx.forceSet(RuntimeMode,RuntimeMode.fromEnvironment(!0));
ctx.set(CopilotTokenManager,getTestingCopilotTokenManager());
ctx.forceSet(UrlOpener,newTestUrlOpener());
awaitsetupTelemetry(ctx,extensionContext,"copilot-test",!0);
}else{
ctx.set(CopilotTokenManager,newVSCodeCopilotTokenManager());
ctx.forceSet(ExpConfigMaker,newExpConfigFromTAS());
awaitsetupTelemetry(ctx,extensionContext,extensionContext.extension.packageJSON.name,vscode.env.isTelemetryEnabled);
}
//设置其他服务和装备
ctx.set(LocationFactory,newExtensionLocationFactory());
ctx.set(TextDocumentManager,newExtensionTextDocumentManager(ctx));
ctx.set(WorkspaceFileSystem,newExtensionWorkspaceFileSystem());
ctx.set(CommitFileResolver,newExtensionCommitFileResolver());
ctx.set(hy.FileSystem,extensionFileSystem);
ctx.set(NetworkConfiguration,newVSCodeNetworkConfiguration());
//回来创立的上下文
returnctx;
}

咱们先来看第一行代码:

letctx=createProductionContext(newVSCodeConfigProvider())

它是经过createProductionContext 这个办法创立了一个Context,参数是一个VSCodeConfigProvider 的实例。

那么首先看看VSCodeConfigProvider

varCopilotConfigPrefix="github.copilot";
varVSCodeConfigProvider=classextendsConfigProvider{
constructor(){
super();
this.config=vscode.workspace.getConfiguration(CopilotConfigPrefix),vscode.workspace.onDidChangeConfiguration(changeEvent=>{
changeEvent.affectsConfiguration(CopilotConfigPrefix)&&(this.config=vscode.workspace.getConfiguration(CopilotConfigPrefix));
});
}
//...
}

仅看一下这个constructor咱们就知道是拉取了vscode的装备项,prefix为github.copilot ,而且监听了config change重新赋值给this.config。

然后再看一下createProductionContext的完结:

functioncreateProductionContext(configProvider){
//创立一个新的上下文
letctx=newContext();
//设置各种服务和装备
ctx.set(ConfigProvider,configProvider);
ctx.set(Clock,newClock());
ctx.set(BuildInfo,newBuildInfo());
setupRudimentaryLogging(ctx);
logger.debug(ctx,"Initializingmaincontext");
ctx.set(CompletionsCache,newCompletionsCache());
ctx.set(CopilotTokenNotifier,newCopilotTokenNotifier());
ctx.set(CertificateReaderCache,newCertificateReaderCache());
ctx.set(RootCertificateReader,getRootCertificateReader(ctx));
ctx.set(ProxySocketFactory,getProxySocketFactory(ctx));
ctx.set(Fetcher,newHelixFetcher(ctx));
ctx.set(LanguageDetection,getLanguageDetection(ctx));
ctx.set(Features,newFeatures(ctx));
ctx.set(PostInsertionNotifier,newPostInsertionNotifier());
ctx.set(TelemetryUserConfig,newTelemetryUserConfig(ctx));
ctx.set(TelemetryEndpointUrl,newTelemetryEndpointUrl());
ctx.set(TelemetryReporters,newTelemetryReporters());
ctx.set(HeaderContributors,newHeaderContributors());
ctx.set(UserErrorNotifier,newUserErrorNotifier(ctx));
ctx.set(ContextualFilterManager,newContextualFilterManager());
ctx.set(OpenAIFetcher,newLiveOpenAIFetcher());
ctx.set(BlockModeConfig,newConfigBlockModeConfig());
ctx.set(UrlOpener,newRealUrlOpener());
ctx.set(ExpConfigMaker,newExpConfigNone());
ctx.set(PromiseQueue,newPromiseQueue());
ctx.set(uD.SnippetOrchestrator,newuD.SnippetOrchestrator());
ctx.set(ForceMultiLine,ForceMultiLine.default);
//回来创立的上下文
returnctx;
}

可以看到这个办法首先创立了一个ctx,然后设置了一系列的类与实例,这个Context类似于依靠注入容器办理的作用:

varContext=class{
constructor(baseContext){
this.baseContext=baseContext;
this.constructionStack=[];
this.instances=newMap();
letstack=newError().stack?.split(`
`);
stack&&this.constructionStack.push(...stack.slice(1));
}
static{
__name(this,"Context");
}
get(ctor){
letvalue=this.tryGet(ctor);
if(value)returnvalue;
thrownewError(`Noinstanceof${ctor.name}hasbeenregistered.`);
}
tryGet(ctor){
letvalue=this.instances.get(ctor);
if(value)returnvalue;
if(this.baseContext)returnthis.baseContext.tryGet(ctor);
}
set(ctor,instance){
if(this.tryGet(ctor))thrownewError(`Aninstanceof${ctor.name}hasalreadybeenregistered.UseforceSet()ifyou'resureit'sagoodidea.`);
this.assertIsInstance(ctor,instance),this.instances.set(ctor,instance);
}
forceSet(ctor,instance){
this.assertIsInstance(ctor,instance),this.instances.set(ctor,instance);
}
assertIsInstance(ctor,instance){
if(!(instanceinstanceofctor)){
letinst=JSON.stringify(instance);
thrownewError(`Theinstanceyou'retryingtoregisterfor${ctor.name}isnotaninstanceofit(${inst}).`);
}
}
toString(){
letlines=`Contextcreatedat:
`;
for(letstackEntryofthis.constructionStack||[])lines+=`${stackEntry}
`;
returnlines+=this.baseContext?.toString()??"",lines;
}
};

在main Context的初始化过程中,首先初始化了三个类:

  • ConfigProvider ,也便是刚刚传进来的那个VSCodeConfigProvider。
  • Clock ,现在看起来便是完结了一个Date.now()。
  • BuildInfo ,封装了关于package.json的相关信息。

然后调用了setupRudimentaryLogging 办法:

functionsetupRudimentaryLogging(ctx){
ctx.set(RuntimeMode,RuntimeMode.fromEnvironment(!1)),
ctx.set(LogVerbose,newLogVerbose(isVerboseLoggingEnabled(ctx))),
ctx.set(LogTarget,newConsoleLog(console));
}

这儿边又初始化了三个类:

  • RuntimeMode ,实际上记载了几个关键的flag:

    • debug ,由-debug参数或GITHUB_COPILOT_DEBUG的环境变量决议。
    • verboseLogging ,由COPILOT_AGENT_VERBOSE决议。
    • telemetryLogging ,由COPILOT_LOG_TELEMETRY 决议。
    • testMode ,在这儿是false
    • recordInput ,由-record参数或GITHUB_COPILOT_RECORD 决议。
  • LogVerbose ,记载是否是verboseLogging

  • LogTarget ,注册为ConsoleLog

接着打了一行debug日志:”Initializing main context”,这应该是copilot的第一行日志。

接着初始化了一堆服务:

  • CompletionsCache ,这个cache默许是LRU(100)
  • CopilotTokenNotifier ,这是一个事情通知器,里边封装了一个emit办法。
  • CertificateReaderCache ,这是一个key为platform,value为Reader的Map。
  • RootCertificateReader ,真正的证书Reader,用来获取rootCA。
  • ProxySocketFactory ,实际上是一个KerberosProxySocketFactory,运用Kerberos进行身份认证。
  • Fetcher,是一个HelixFetcher的实例,用来发送HTTP恳求。
  • LanguageDetection ,实际上是由FilenameAndExensionLanguageDetectionNotebookLanguageDetection组成,统一走CachingLanguageDetection来缓存,揣度具体为哪个language的战略还有点杂乱。
  • Features,包括一些实验特性。
  • PostInsertionNotifier ,纯粹便是一个eventEmitter。
  • TelemetryUserConfig ,关于telemetry的一些装备,基本上是经过CopilotTokenNotifier这个事情监听得到的。
  • TelemetryEndpointUrl ,保护telemetry的url地址,默许是copilot-telemetry.githubusercontent.com/telemetry。
  • TelemetryReporters ,保护了telemetry的reporter。
  • HeaderContributors ,保护一个Contributors 的列表。
  • UserErrorNotifier ,用来处理证书相关的异常?
  • ContextualFilterManager ,办理ContextualFilter。
  • OpenAIFetcher ,被实例化为LiveOpenAIFetcher ,适配了OpenAI的回来格式。
  • BlockModeConfig ,实例化为ConfigBlockModeConfig,跟indent装备有关。
  • UrlOpener ,被实例化为RealUrlOpener ,完结了一个open办法。
  • ExpConfigMaker ,实验特性标记,这儿被实例化为一个ExpConfigNone ,默许不拉实验特性。
  • PromiseQueue ,一个promise队列。
  • SnippetOrchestrator ,snippet编排。
  • ForceMultiLine ,看起来是强制multiline。

至此整个createProductionContext 的流程就结束了。接下来看一下createExtensionContext 的流程:

logTarget=newMultiLog([newConsoleLog(console),newOutputChannelLog(outputChannel)]);
ctx.forceSet(LogTarget,logTarget);

首先重置了一下logTarget,一起向console和outputchannel输出。

然后将EditorAndPluginInfo 设置为VSCodeEditorInfo ,封装了一些基本的信息。

接着初始化了initProxyEnvironment proxy的逻辑,监听proxy的改变保证proxy能够正常。

接着初始化了以下服务:

  • NotificationSender ,一个通知的服务,调用showWarningMessage
  • EditorSession ,办理session周期
  • Extension , Extension相关,context存在这儿。
  • EditorExperimentFilters ,设置为VSCodeEditorExperimentFilters ,看起来是加了X-VSCode-Build,X-VSCode-Language两个特点。

然后调用了setupExperimentationService

functionsetupExperimentationService(ctx){
letfeatures=ctx.get(Features);
features.registerStaticFilters(createAllFilters(ctx)),
features.registerDynamicFilter("X-Copilot-OverrideEngine",()=>getConfig(ctx,ConfigKey.DebugOverrideEngine));
}

这儿边便是设置了一些基础的头信息:

  • X-VSCode-AppVersion
  • X-MSEdge-ClientId
  • X-VSCode-ExtensionName
  • X-VSCode-ExtensionVersion
  • X-VSCode-TargetPopulation

接着界说了两个服务:

  • SymbolDefinitionProvider ,界说了SymbolDefinition。
  • CopilotExtensionStatus ,界说了当前插件的状况和报错信息。

接着区分了环境,正式环境中的界说

  • CopilotTokenManager,这个是指copilot的登录token办理。
  • ExpConfigMaker 重新指向为ExpConfigFromTAS ,也便是默许从TAS平台上拉取实验特性数据。

接着初始化setupTelemetry 的逻辑。

然后还有一系列其他服务的设置:

  • LocationFactory,初始化一个location的东西类。
  • TextDocumentManager ,负责TextDocument相关的处理。
  • WorkspaceFileSystem ,workspace关于文件相关的处理。
  • CommitFileResolver ,提交文件相关的处理。
  • FileSystem ,文件相关的处理。
  • NetworkConfiguration ,主要是访问github的URL地址。

进口主逻辑梳理

进口主逻辑细枝末节比较多,这儿画图做个总结:

copilot源码详细剖析(二)activate进口剖析

image

在进口初始化中,最重要的是标红的两步:

  • registerGhostTextSupport ,这个注册了整个InlineCompletion,也便是咱们的代码提示都是走这个逻辑。
  • snippetOrchestrator.startThreading ,这个敞开了一个worker线程,接下来咱们详细剖析一下。

关于worker线程

copilot将比较耗时的操作都放到了worker线程去,比方下面的init办法:

varpromptlib=Ns(Dc());
varworker=null;
varhandlers=newMap();
varnextHandlerId=0;
functioninit(ctx,use_worker_threads,logger){
if(!use_worker_threads){
letlocalPromptlib=(uL(),nT(Pre));
for(letfnofallFuns)updatePromptLibProxyFunction(fn,localPromptlib[fn]);
return;
}
for(letfnofworkerFuns)updatePromptLibProxyFunction(fn,proxy(ctx,logger,fn));
promptLibProxy.getPrompt=getPromptProxy(ctx,logger);
worker=X0.createWorker();
handlers.clear();
nextHandlerId=0;
worker.on("message",({id,err,code,res})=>{
lethandler=handlers.get(id);
logger.debug(ctx,`Response${id}-${res},${err}`);
if(handler){
handlers.delete(id);
if(err){
err.code=code;
handler.reject(err);
}else{
handler.resolve(res);
}
}
});
functionhandleError(maybeError){
leterr;
if(maybeErrorinstanceofError){
err=maybeError;
if(err.code==="MODULE_NOT_FOUND"&&err.message?.endsWith("worker.js'")){
err=newError("Failedtoloadworker.js");
err.code="CopilotPromptLoadFailure";
}
letourStack=newError().stack;
if(err.stack&&ourStack?.match(/^Errorn/)){
err.stack+=ourStack.replace(/^Error/,"");
}
}elseif(maybeError?.name==="ExitStatus"&&typeofmaybeError.status=="number"){
err=newError(`worker.jsexitedwithstatus${maybeError.status}`);
err.code=`CopilotPromptWorkerExit${maybeError.status}`;
}else{
err=newError(`Non-errorthrown:${maybeError}`);
}
for(lethandlerofhandlers.values())handler.reject(err);
handlers.clear();
}
__name(handleError,"handleError");
worker.on("error",handleError);
}
__name(init,"init");
functionterminate(){
if(worker){
worker.removeAllListeners();
worker.terminate();
worker=null;
handlers.clear();
}
}
__name(terminate,"terminate");
varworkerFuns=[
"getFunctionPositions",
"isEmptyBlockStart",
"isBlockBodyFinished",
"getNodeStart",
"getCallSites",
"parsesWithoutError"
];
vardirectFuns=[
"isSupportedLanguageId",
"getBlockCloseToken",
"getPrompt"
];
varallFuns=[...workerFuns,...directFuns];
functionproxy(ctx,logger,fn){
returnfunction(...args){
letid=nextHandlerId++;
returnnewPromise((resolve,reject)=>{
handlers.set(id,{resolve:resolve,reject:reject});
logger.debug(ctx,`Proxy${fn}`);
worker?.postMessage({id:id,fn:fn,args:args});
});
};
}
__name(proxy,"proxy");
functiongetPromptProxy(ctx,logger){
returnfunction(_fileSystem,...args){
letid=nextHandlerId++;
returnnewPromise((resolve,reject)=>{
handlers.set(id,{resolve:resolve,reject:reject});
logger.debug(ctx,`ProxygetPrompt-${id}`);
worker?.postMessage({id:id,fn:"getPrompt",args:args});
});
};
}
__name(getPromptProxy,"getPromptProxy");
functionupdatePromptLibProxyFunction(fn,impl){
promptLibProxy[fn]=impl;
}
__name(updatePromptLibProxyFunction,"updatePromptLibProxyFunction");
varpromptLibProxy={
isEmptyBlockStart:X0.isEmptyBlockStart,
isBlockBodyFinished:X0.isBlockBodyFinished,
isSupportedLanguageId:X0.isSupportedLanguageId,
getBlockCloseToken:X0.getBlockCloseToken,
getFunctionPositions:X0.getFunctionPositions,
getNodeStart:X0.getNodeStart,
getPrompt:X0.getPrompt,
getCallSites:X0.getCallSites,
parsesWithoutError:X0.parsesWithoutError
};

可以看到,放在worker线程的办法主要是5个:

  • getFunctionPositions
  • isEmptyBlockStart
  • isBlockBodyFinished
  • getNodeStart
  • getCallSites
  • parsesWithoutError

其间,还有一个办法也被独自署理到worker线程:

  • getPrompt

除了这个worker线程以外,还开了别的一个worker线程workerProxy:

workerFns=["getNeighborSnippets","extractLocalImportContext","sleep"],WorkerProxy=class{
constructor(){
this.nextHandlerId=0;
this.handlers=newMap();
this.fns=newMap();
this.extractLocalImportContext=extractLocalImportContext;
this.getNeighborSnippets=getNeighborSnippets;
this.sleep=sleep;
!Kf.isMainThread&&Kf.workerData?.port&&(wT(),process.cwd=()=>Kf.workerData.cwd,this.configureWorkerResponse(Kf.workerData.port));
}
static{
__name(this,"WorkerProxy");
}
initWorker(){
let{
port1:port1,
port2:port2
}=newKf.MessageChannel();
this.port=port1,this.worker=newKf.Worker((0,yre.resolve)(__dirname,"..","dist","workerProxy.js"),{
workerData:{
port:port2,
cwd:process.cwd()
},
transferList:[port2]
}),this.port.on("message",m=>this.handleMessage(m)),this.port.on("error",e=>this.handleError(e));
}
startThreading(){
if(this.worker)thrownewError("Workerthreadalreadyinitialized.");
this.proxyFunctions(),this.initWorker();
}
stopThreading(){
this.worker&&(this.worker.terminate(),this.worker.removeAllListeners(),this.worker=void0,this.unproxyFunctions(),this.handlers.clear());
}
proxyFunctions(){
for(letfnofworkerFns)this.fns.set(fn,this[fn]),this.proxy(fn);
}
unproxyFunctions(){
for(letfnofworkerFns){
letoriginalFn=this.fns.get(fn);
if(originalFn)this[fn]=originalFn;elsethrownewError(`Unproxyfunctionnotfound:${fn}`);
}
}
configureWorkerResponse(port){
this.port=port,this.port.on("message",async({
id:id,
fn:fn,
args:args
})=>{
letproxiedFunction=this[fn];
if(!proxiedFunction)thrownewError(`Functionnotfound:${fn}`);
try{
letres=awaitproxiedFunction.apply(this,args);
this.port.postMessage({
id:id,
res:res
});
}catch(err){
if(!(errinstanceofError))throwerr;
typeoferr.code=="string"?this.port.postMessage({
id:id,
err:err,
code:err.code
}):this.port.postMessage({
id:id,
err:err
});
}
});
}
handleMessage({
id:id,
err:err,
code:code,
res:res
}){
lethandler=this.handlers.get(id);
handler&&(this.handlers.delete(id),err?(err.code=code,handler.reject(err)):handler.resolve(res));
}
handleError(maybeError){
console.log(maybeError);
leterr;
if(maybeErrorinstanceofError){
err=maybeError,err.code==="MODULE_NOT_FOUND"&&err.message?.endsWith("workerProxy.js'")&&(err=newError("FailedtoloadworkerProxy.js"),err.code="CopilotPromptLoadFailure");
letourStack=newError().stack;
err.stack&&ourStack?.match(/^Errorn/)&&(err.stack+=ourStack.replace(/^Error/,""));
}elsemaybeError?.name==="ExitStatus"&&typeofmaybeError.status=="number"?(err=newError(`workerProxy.jsexitedwithstatus${maybeError.status}`),err.code=`CopilotPromptWorkerExit${maybeError.status}`):err=newError(`Non-errorthrown:${maybeError}`);
for(lethandlerofthis.handlers.values())handler.reject(err);
throwerr;
}
proxy(fn){
this[fn]=function(...args){
letid=this.nextHandlerId++;
returnnewPromise((resolve,reject)=>{
this.handlers.set(id,{
resolve:resolve,
reject:reject
}),this.port?.postMessage({
id:id,
fn:fn,
args:args
});
});
};
}
},workerProxy=newWorkerProxy();

这儿的通讯署理和第一个worker线程的署理机制大同小异,这次署理的是与snippets相关的几个办法:

  • getNeighborSnippets
  • extractLocalImportContext
  • sleep

将这些昂贵的操作放在worker线程中,保障了全体主线程的功能不会卡顿。

小结一下

本文主要剖析了copilot进口函数的全体逻辑,最重要的是两大块内容:

  • Context初始化
  • 注册ghostText并敞开worker线程

在Context部分,copilot一切的实例都是经过挂在容器的方法形成单例的,一个优点便是对于一个类可以有多个完结,只需要替换掉不同的Instance即可,这也契合开闭规划原则

在一系列初始化完结之后,copilot登录经往后,会注册到ghostText,敞开inlineCompletion的形式,这也便是咱们在copilot中体验到的代码补全的核心功用。

别的copilot还敞开了两个worker线程,分别署理了snippet相关和Prompt相关的几个函数,这些函数默许在非开发环境下会在worker线程跑,从而保障了主进程更优的功能

上述代码现已提交在Github上,有需要的小伙伴可自取:

github.com/mengjian-gi…