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
,实际上是由FilenameAndExensionLanguageDetection
和NotebookLanguageDetection
组成,统一走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地址。
进口主逻辑梳理
进口主逻辑细枝末节比较多,这儿画图做个总结:
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上,有需要的小伙伴可自取: