11import { ChildProcess } from 'child_process' ;
2- import { copyFile } from 'fs' ;
32import * as path from 'path' ;
43import * as vscode from 'vscode' ;
5- import { Utils } from 'vscode-uri' ;
6- import { LANG_TLAPLUS , LANG_TLAPLUS_CFG , exists , listFiles , replaceExtension } from '../common' ;
4+ import { LANG_TLAPLUS , LANG_TLAPLUS_CFG } from '../common' ;
75import { applyDCollection } from '../diagnostic' ;
86import { ModelCheckResult , ModelCheckResultSource , SpecFiles } from '../model/check' ;
97import { ToolOutputChannel } from '../outputChannels' ;
@@ -15,6 +13,7 @@ import {
1513} from '../panels/checkResultView' ;
1614import { TlcModelCheckerStdoutParser } from '../parsers/tlc' ;
1715import { runTlc , stopProcess } from '../tla2tools' ;
16+ import { ModelResolveMode , resolveModelForUri } from './modelResolver' ;
1817import { TlcCoverageDecorationProvider } from '../tlcCoverage' ;
1918
2019export const CMD_CHECK_MODEL_RUN = 'tlaplus.model.check.run' ;
@@ -27,8 +26,6 @@ export const CTX_TLC_RUNNING = 'tlaplus.tlc.isRunning';
2726export const CTX_TLC_CAN_RUN_AGAIN = 'tlaplus.tlc.canRunAgain' ;
2827
2928const CFG_CREATE_OUT_FILES = 'tlaplus.tlc.modelChecker.createOutFiles' ;
30- const TEMPLATE_CFG_PATH = path . resolve ( __dirname , '../tools/template.cfg' ) ;
31-
3229let checkProcess : ChildProcess | undefined ;
3330let lastCheckFiles : SpecFiles | undefined ;
3431let coverageProvider : TlcCoverageDecorationProvider | undefined ;
@@ -89,26 +86,14 @@ export async function checkModelCustom(
8986 return ;
9087 }
9188 const doc = editor . document ;
92- if ( doc . languageId !== LANG_TLAPLUS ) {
93- vscode . window . showWarningMessage ( 'File in the active editor is not a .tla, it cannot be checked as a model' ) ;
89+ if ( doc . languageId !== LANG_TLAPLUS && doc . languageId !== LANG_TLAPLUS_CFG ) {
90+ vscode . window . showWarningMessage ( 'File in the active editor is not a .tla or .cfg , it cannot be checked as a model' ) ;
9491 return ;
9592 }
96- // Accept .tla files here because TLC configs and TLA+ modules can share the same file:
97- // https://github.com/tlaplus/vscode-tlaplus/issues/220
98- const configFiles = await listFiles ( path . dirname ( doc . uri . fsPath ) ,
99- ( fName ) => fName . endsWith ( '.cfg' ) || fName . endsWith ( '.tla' ) ) ;
100- configFiles . sort ( ) ;
101- const cfgFileName = await vscode . window . showQuickPick (
102- configFiles ,
103- { canPickMany : false , placeHolder : 'Select a model config file' , matchOnDetail : true }
104- ) ;
105- if ( ! cfgFileName || cfgFileName . length === 0 ) {
93+ const specFiles = await getSpecFiles ( doc . uri , true , true , 'customPick' ) ;
94+ if ( ! specFiles ) {
10695 return ;
10796 }
108- const specFiles = new SpecFiles (
109- doc . uri . fsPath ,
110- path . join ( path . dirname ( doc . uri . fsPath ) , cfgFileName )
111- ) ;
11297 await doCheckModel ( specFiles , true , extContext , diagnostic , true ) ;
11398}
11499
@@ -186,7 +171,7 @@ export async function doCheckModel(
186171) : Promise < ModelCheckResult | undefined > {
187172 try {
188173 const procInfo = await runTlc (
189- specFiles . tlaFilePath , path . basename ( specFiles . cfgFilePath ) , showOptionsPrompt , extraOpts ) ;
174+ specFiles . tlaFilePath , specFiles . cfgFilePath , showOptionsPrompt , extraOpts ) ;
190175 if ( procInfo === undefined ) {
191176 // Command cancelled by user, make sure UI state is reset
192177 vscode . commands . executeCommand ( 'setContext' , CTX_TLC_CAN_RUN_AGAIN , ! ! lastCheckFiles ) ;
@@ -203,7 +188,7 @@ export async function doCheckModel(
203188 updateStatusBarItem ( false , lastCheckFiles ) ;
204189 } ) ;
205190 if ( showCheckResultView ) {
206- attachFileSaver ( specFiles . tlaFilePath , checkProcess ) ;
191+ attachFileSaver ( specFiles , checkProcess ) ;
207192 revealEmptyCheckResultView ( extContext ) ;
208193 }
209194 const resultHolder = new CheckResultHolder ( ) ;
@@ -242,116 +227,47 @@ export async function doCheckModel(
242227 return undefined ;
243228}
244229
245- function attachFileSaver ( tlaFilePath : string , proc : ChildProcess ) {
230+ function attachFileSaver ( specFiles : SpecFiles , proc : ChildProcess ) {
246231 const createOutFiles = vscode . workspace . getConfiguration ( ) . get < boolean > ( CFG_CREATE_OUT_FILES ) ;
247232 if ( typeof ( createOutFiles ) === 'undefined' || createOutFiles ) {
248- const outFilePath = replaceExtension ( tlaFilePath , ' out' ) ;
233+ const outFilePath = path . join ( specFiles . outputDir , ` ${ specFiles . modelName } . out` ) ;
249234 saveStreamToFile ( proc . stdout , outFilePath ) ;
250235 }
251236}
252237
253238/**
254239 * Finds all files that needed to run model check.
255240 */
256- export async function getSpecFiles ( fileUri : vscode . Uri , warn = true , prefix = 'MC' ) : Promise < SpecFiles | undefined > {
257- let specFiles ;
258-
259- // a) Check the given input if it exists.
260- specFiles = await checkSpecFiles ( fileUri , false ) ;
261- if ( specFiles ) {
262- return specFiles ;
263- }
264- // b) Check alternatives:
265- // Unless the given filePath already starts with 'MC', prepend MC to the name
266- // and check if it exists. If yes, it becomes the spec file. If not, fall back
267- // to the original file. The assumptions is that a user usually has Spec.tla
268- // open in the editor and doesn't want to switch to MC.tla before model-checking.
269- // TODO: Ideally, we wouldn't just check filenames here but check the parse result
270- // TODO: if the module in MCSpec.tla actually extends the module in Spec.
271- const b = Utils . basename ( fileUri ) ;
272- if ( ! b . startsWith ( prefix ) && ! b . endsWith ( '.cfg' ) ) {
273- const str = fileUri . toString ( ) ;
274- const n = str . substr ( 0 , str . lastIndexOf ( b ) ) + prefix + b ;
275- const filePath = vscode . Uri . parse ( n ) . fsPath ;
276- specFiles = new SpecFiles ( filePath , replaceExtension ( filePath , 'cfg' ) ) ;
277- // Here, we make sure that the .cfg *and* the .tla exist.
278- let canRun = true ;
279- canRun = await checkModelExists ( specFiles . cfgFilePath , warn ) ;
280- canRun = canRun && await checkModuleExists ( specFiles . tlaFilePath , warn ) ;
281- if ( canRun ) {
282- return specFiles ;
283- }
284- }
285- // c) Deliberately trigger the warning dialog by checking the given input again
286- // knowing that it doesn't exist.
287- return await checkSpecFiles ( fileUri , warn ) ;
288- }
289-
290- async function checkSpecFiles ( fileUri : vscode . Uri , warn = true ) : Promise < SpecFiles | undefined > {
291- const filePath = fileUri . fsPath ;
292- let specFiles ;
293- let canRun = true ;
294- if ( filePath . endsWith ( '.cfg' ) ) {
295- specFiles = new SpecFiles ( replaceExtension ( filePath , 'tla' ) , filePath ) ;
296- canRun = await checkModuleExists ( specFiles . tlaFilePath , warn ) ;
297- } else if ( filePath . endsWith ( '.tla' ) ) {
298- specFiles = new SpecFiles ( filePath , replaceExtension ( filePath , 'cfg' ) ) ;
299- canRun = await checkModelExists ( specFiles . cfgFilePath , warn ) ;
300- }
301- return canRun ? specFiles : undefined ;
302- }
303-
304- async function checkModuleExists ( modulePath : string , warn = true ) : Promise < boolean > {
305- const moduleExists = await exists ( modulePath ) ;
306- if ( ! moduleExists && warn ) {
307- const moduleFile = path . basename ( modulePath ) ;
308- vscode . window . showWarningMessage ( `Corresponding TLA+ module file ${ moduleFile } doesn't exist.` ) ;
309- }
310- return moduleExists ;
311- }
312-
313- async function checkModelExists ( cfgPath : string , warn = true ) : Promise < boolean > {
314- const cfgExists = await exists ( cfgPath ) ;
315- if ( ! cfgExists && warn ) {
316- showConfigAbsenceWarning ( cfgPath ) ;
241+ export async function getSpecFiles (
242+ fileUri : vscode . Uri ,
243+ warn = true ,
244+ interactive = true ,
245+ mode : ModelResolveMode = 'adjacent'
246+ ) : Promise < SpecFiles | undefined > {
247+ const resolved = await resolveModelForUri ( fileUri , warn , interactive , mode ) ;
248+ if ( ! resolved ) {
249+ return undefined ;
317250 }
318- return cfgExists ;
251+ return new SpecFiles (
252+ resolved . tlaPath ,
253+ resolved . cfgPath ,
254+ resolved . modelName ,
255+ resolved . outputDir
256+ ) ;
319257}
320258
321259function updateStatusBarItem ( active : boolean , specFiles : SpecFiles | undefined ) {
322260 statusBarItem . text = 'TLC' + ( active ?
323- ( specFiles === undefined ? '' : ' (' + specFiles . tlaFileName + '/' + specFiles . cfgFileName + ')' )
261+ ( specFiles === undefined ? '' : ` (Model: ${ specFiles . modelName } )` )
324262 + ' $(gear~spin)' : '' ) ;
325263 statusBarItem . tooltip = 'TLA+ model checking' + ( active ?
326- ( specFiles === undefined ? '' : ' of ' + specFiles . tlaFileName + '/' + specFiles . cfgFileName )
264+ ( specFiles === undefined ? '' : ` of model ${ specFiles . modelName } ` )
327265 + ' is running' : ' result' ) ;
328266 statusBarItem . command = CMD_CHECK_MODEL_DISPLAY ;
329267 statusBarItem . show ( ) ;
330268 vscode . commands . executeCommand ( 'setContext' , CTX_TLC_RUNNING , active ) ;
331269}
332270
333- function showConfigAbsenceWarning ( cfgPath : string ) {
334- const fileName = path . basename ( cfgPath ) ;
335- const createOption = 'Create model file' ;
336- vscode . window . showWarningMessage ( `Model file ${ fileName } doesn't exist. Cannot check model.` , createOption )
337- . then ( ( option ) => {
338- if ( option === createOption ) {
339- createModelFile ( cfgPath ) ;
340- }
341- } ) ;
342- }
343-
344- async function createModelFile ( cfgPath : string ) {
345- copyFile ( TEMPLATE_CFG_PATH , cfgPath , ( err ) => {
346- if ( err ) {
347- console . warn ( `Error creating config file: ${ err } ` ) ;
348- vscode . window . showWarningMessage ( `Cannot create model file: ${ err } ` ) ;
349- return ;
350- }
351- vscode . workspace . openTextDocument ( cfgPath )
352- . then ( doc => vscode . window . showTextDocument ( doc ) ) ;
353- } ) ;
354- }
355271
356272export function mapTlcOutputLine ( line : string ) : string | undefined {
357273 if ( line === '' ) {
0 commit comments